mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
feat(Send Email Node): New operation sendAndWait (#12775)
This commit is contained in:
@@ -135,7 +135,6 @@ describe('Node Creator', () => {
|
||||
'OpenThesaurus',
|
||||
'Spontit',
|
||||
'Vonage',
|
||||
'Send Email',
|
||||
'Toggl Trigger',
|
||||
];
|
||||
const doubleActionNode = 'OpenWeatherMap';
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { SEND_AND_WAIT_OPERATION, type IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { EmailSendV2, versionDescription } from '../../v2/EmailSendV2.node';
|
||||
import * as utils from '../../v2/utils';
|
||||
|
||||
const transporter = { sendMail: jest.fn() };
|
||||
|
||||
jest.mock('../../v2/utils', () => {
|
||||
const originalModule = jest.requireActual('../../v2/utils');
|
||||
return {
|
||||
...originalModule,
|
||||
configureTransport: jest.fn(() => transporter),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test EmailSendV2, email => sendAndWait', () => {
|
||||
let emailSendV2: EmailSendV2;
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
emailSendV2 = new EmailSendV2(versionDescription);
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should send message and put execution to wait', async () => {
|
||||
const items = [{ json: { data: 'test' } }];
|
||||
//node
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(SEND_AND_WAIT_OPERATION);
|
||||
|
||||
//operation
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('from@mail.com');
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('to@mail.com');
|
||||
mockExecuteFunctions.getInstanceId.mockReturnValue('instanceId');
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({});
|
||||
mockExecuteFunctions.putExecutionToWait.mockImplementation();
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
|
||||
//getSendAndWaitConfig
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('my message');
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('my subject');
|
||||
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('http://localhost/waiting-webhook');
|
||||
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('nodeID');
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({});
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('approval');
|
||||
|
||||
// configureWaitTillDate
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({}); //options.limitWaitTime.values
|
||||
|
||||
const result = await emailSendV2.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual([items]);
|
||||
expect(utils.configureTransport).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.putExecutionToWait).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(transporter.sendMail).toHaveBeenCalledWith({
|
||||
from: 'from@mail.com',
|
||||
html: expect.stringContaining('href="http://localhost/waiting-webhook/nodeID?approved=true"'),
|
||||
subject: 'my subject',
|
||||
to: 'to@mail.com',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,11 +5,15 @@ import type {
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { NodeConnectionType, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
|
||||
import * as send from './send.operation';
|
||||
import * as sendAndWait from './sendAndWait.operation';
|
||||
import { smtpConnectionTest } from './utils';
|
||||
import { sendAndWaitWebhooksDescription } from '../../../utils/sendAndWait/descriptions';
|
||||
import { sendAndWaitWebhook } from '../../../utils/sendAndWait/utils';
|
||||
|
||||
const versionDescription: INodeTypeDescription = {
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Send Email',
|
||||
name: 'emailSend',
|
||||
icon: 'fa:envelope',
|
||||
@@ -30,6 +34,7 @@ const versionDescription: INodeTypeDescription = {
|
||||
testedBy: 'smtpConnectionTest',
|
||||
},
|
||||
],
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
@@ -47,7 +52,7 @@ const versionDescription: INodeTypeDescription = {
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'hidden',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
default: 'send',
|
||||
options: [
|
||||
@@ -56,9 +61,15 @@ const versionDescription: INodeTypeDescription = {
|
||||
value: 'send',
|
||||
action: 'Send an Email',
|
||||
},
|
||||
{
|
||||
name: 'Send and Wait for Response',
|
||||
value: SEND_AND_WAIT_OPERATION,
|
||||
action: 'Send message and wait for response',
|
||||
},
|
||||
],
|
||||
},
|
||||
...send.description,
|
||||
...sendAndWait.description,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -73,13 +84,22 @@ export class EmailSendV2 implements INodeType {
|
||||
}
|
||||
|
||||
methods = {
|
||||
credentialTest: { smtpConnectionTest: send.smtpConnectionTest },
|
||||
credentialTest: { smtpConnectionTest },
|
||||
};
|
||||
|
||||
webhook = sendAndWaitWebhook;
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
let returnData: INodeExecutionData[][] = [];
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
returnData = await send.execute.call(this);
|
||||
if (operation === SEND_AND_WAIT_OPERATION) {
|
||||
returnData = await sendAndWait.execute.call(this);
|
||||
}
|
||||
|
||||
if (operation === 'send') {
|
||||
returnData = await send.execute.call(this);
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
23
packages/nodes-base/nodes/EmailSend/v2/descriptions.ts
Normal file
23
packages/nodes-base/nodes/EmailSend/v2/descriptions.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const fromEmailProperty: INodeProperties = {
|
||||
displayName: 'From Email',
|
||||
name: 'fromEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'admin@example.com',
|
||||
description:
|
||||
'Email address of the sender. You can also specify a name: Nathan Doe <nate@n8n.io>.',
|
||||
};
|
||||
|
||||
export const toEmailProperty: INodeProperties = {
|
||||
displayName: 'To Email',
|
||||
name: 'toEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'info@example.com',
|
||||
description:
|
||||
'Email address of the recipient. You can also specify a name: Nathan Doe <nate@n8n.io>.',
|
||||
};
|
||||
@@ -1,43 +1,22 @@
|
||||
import type {
|
||||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeCredentialTestResult,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
import { createTransport } from 'nodemailer';
|
||||
import type SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { fromEmailProperty, toEmailProperty } from './descriptions';
|
||||
import { configureTransport, type EmailSendOptions } from './utils';
|
||||
import { appendAttributionOption } from '../../../utils/descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
// TODO: Add choice for text as text or html (maybe also from name)
|
||||
{
|
||||
displayName: 'From Email',
|
||||
name: 'fromEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'admin@example.com',
|
||||
description:
|
||||
'Email address of the sender. You can also specify a name: Nathan Doe <nate@n8n.io>.',
|
||||
},
|
||||
{
|
||||
displayName: 'To Email',
|
||||
name: 'toEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'info@example.com',
|
||||
description:
|
||||
'Email address of the recipient. You can also specify a name: Nathan Doe <nate@n8n.io>.',
|
||||
},
|
||||
fromEmailProperty,
|
||||
toEmailProperty,
|
||||
|
||||
{
|
||||
displayName: 'Subject',
|
||||
@@ -194,72 +173,11 @@ const displayOptions = {
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
type EmailSendOptions = {
|
||||
appendAttribution?: boolean;
|
||||
allowUnauthorizedCerts?: boolean;
|
||||
attachments?: string;
|
||||
ccEmail?: string;
|
||||
bccEmail?: string;
|
||||
replyTo?: string;
|
||||
};
|
||||
|
||||
function configureTransport(credentials: IDataObject, options: EmailSendOptions) {
|
||||
const connectionOptions: SMTPTransport.Options = {
|
||||
host: credentials.host as string,
|
||||
port: credentials.port as number,
|
||||
secure: credentials.secure as boolean,
|
||||
};
|
||||
|
||||
if (credentials.secure === false) {
|
||||
connectionOptions.ignoreTLS = credentials.disableStartTls as boolean;
|
||||
}
|
||||
|
||||
if (typeof credentials.hostName === 'string' && credentials.hostName) {
|
||||
connectionOptions.name = credentials.hostName;
|
||||
}
|
||||
|
||||
if (credentials.user || credentials.password) {
|
||||
connectionOptions.auth = {
|
||||
user: credentials.user as string,
|
||||
pass: credentials.password as string,
|
||||
};
|
||||
}
|
||||
|
||||
if (options.allowUnauthorizedCerts === true) {
|
||||
connectionOptions.tls = {
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
}
|
||||
|
||||
return createTransport(connectionOptions);
|
||||
}
|
||||
|
||||
export async function smtpConnectionTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data!;
|
||||
const transporter = configureTransport(credentials, {});
|
||||
try {
|
||||
await transporter.verify();
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: error.message,
|
||||
};
|
||||
} finally {
|
||||
transporter.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
const instanceId = this.getInstanceId();
|
||||
const credentials = await this.getCredentials('smtp');
|
||||
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
let item: INodeExecutionData;
|
||||
@@ -274,8 +192,6 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
||||
const emailFormat = this.getNodeParameter('emailFormat', itemIndex) as string;
|
||||
const options = this.getNodeParameter('options', itemIndex, {}) as EmailSendOptions;
|
||||
|
||||
const credentials = await this.getCredentials('smtp');
|
||||
|
||||
const transporter = configureTransport(credentials, options);
|
||||
|
||||
const mailOptions: IDataObject = {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { fromEmailProperty, toEmailProperty } from './descriptions';
|
||||
import { configureTransport } from './utils';
|
||||
import { createEmailBody } from '../../../utils/sendAndWait/email-templates';
|
||||
import {
|
||||
configureWaitTillDate,
|
||||
createButton,
|
||||
getSendAndWaitConfig,
|
||||
getSendAndWaitProperties,
|
||||
} from '../../../utils/sendAndWait/utils';
|
||||
|
||||
export const description: INodeProperties[] = getSendAndWaitProperties(
|
||||
[fromEmailProperty, toEmailProperty],
|
||||
'email',
|
||||
);
|
||||
|
||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const fromEmail = this.getNodeParameter('fromEmail', 0) as string;
|
||||
const toEmail = this.getNodeParameter('toEmail', 0) as string;
|
||||
|
||||
const config = getSendAndWaitConfig(this);
|
||||
const buttons: string[] = [];
|
||||
for (const option of config.options) {
|
||||
buttons.push(createButton(config.url, option.label, option.value, option.style));
|
||||
}
|
||||
|
||||
const instanceId = this.getInstanceId();
|
||||
|
||||
const htmlBody = createEmailBody(config.message, buttons.join('\n'), instanceId);
|
||||
|
||||
const mailOptions: IDataObject = {
|
||||
from: fromEmail,
|
||||
to: toEmail,
|
||||
subject: config.title,
|
||||
html: htmlBody,
|
||||
};
|
||||
|
||||
const credentials = await this.getCredentials('smtp');
|
||||
const transporter = configureTransport(credentials, {});
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
const waitTill = configureWaitTillDate(this);
|
||||
|
||||
await this.putExecutionToWait(waitTill);
|
||||
return [this.getInputData()];
|
||||
}
|
||||
70
packages/nodes-base/nodes/EmailSend/v2/utils.ts
Normal file
70
packages/nodes-base/nodes/EmailSend/v2/utils.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
INodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
import { createTransport } from 'nodemailer';
|
||||
import type SMTPTransport from 'nodemailer/lib/smtp-transport';
|
||||
|
||||
export type EmailSendOptions = {
|
||||
appendAttribution?: boolean;
|
||||
allowUnauthorizedCerts?: boolean;
|
||||
attachments?: string;
|
||||
ccEmail?: string;
|
||||
bccEmail?: string;
|
||||
replyTo?: string;
|
||||
};
|
||||
|
||||
export function configureTransport(credentials: IDataObject, options: EmailSendOptions) {
|
||||
const connectionOptions: SMTPTransport.Options = {
|
||||
host: credentials.host as string,
|
||||
port: credentials.port as number,
|
||||
secure: credentials.secure as boolean,
|
||||
};
|
||||
|
||||
if (credentials.secure === false) {
|
||||
connectionOptions.ignoreTLS = credentials.disableStartTls as boolean;
|
||||
}
|
||||
|
||||
if (typeof credentials.hostName === 'string' && credentials.hostName) {
|
||||
connectionOptions.name = credentials.hostName;
|
||||
}
|
||||
|
||||
if (credentials.user || credentials.password) {
|
||||
connectionOptions.auth = {
|
||||
user: credentials.user as string,
|
||||
pass: credentials.password as string,
|
||||
};
|
||||
}
|
||||
|
||||
if (options.allowUnauthorizedCerts === true) {
|
||||
connectionOptions.tls = {
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
}
|
||||
|
||||
return createTransport(connectionOptions);
|
||||
}
|
||||
|
||||
export async function smtpConnectionTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data!;
|
||||
const transporter = configureTransport(credentials, {});
|
||||
try {
|
||||
await transporter.verify();
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: error.message,
|
||||
};
|
||||
} finally {
|
||||
transporter.close();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { labelFields, labelOperations } from './LabelDescription';
|
||||
import { getGmailAliases, getLabels, getThreadMessages } from './loadOptions';
|
||||
import { messageFields, messageOperations } from './MessageDescription';
|
||||
import { threadFields, threadOperations } from './ThreadDescription';
|
||||
import { sendAndWaitWebhooks } from '../../../../utils/sendAndWait/descriptions';
|
||||
import { sendAndWaitWebhooksDescription } from '../../../../utils/sendAndWait/descriptions';
|
||||
import type { IEmail } from '../../../../utils/sendAndWait/interfaces';
|
||||
import {
|
||||
configureWaitTillDate,
|
||||
@@ -69,7 +69,7 @@ const versionDescription: INodeTypeDescription = {
|
||||
},
|
||||
},
|
||||
],
|
||||
webhooks: sendAndWaitWebhooks,
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
||||
@@ -51,6 +51,9 @@ describe('Test MicrosoftOutlookV2, message => sendAndWait', () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({});
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('approval');
|
||||
|
||||
// configureWaitTillDate
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({}); //options.limitWaitTime.values
|
||||
|
||||
const result = await microsoftOutlook.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual([items]);
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as folder from './folder';
|
||||
import * as folderMessage from './folderMessage';
|
||||
import * as message from './message';
|
||||
import * as messageAttachment from './messageAttachment';
|
||||
import { sendAndWaitWebhooks } from '../../../../../utils/sendAndWait/descriptions';
|
||||
import { sendAndWaitWebhooksDescription } from '../../../../../utils/sendAndWait/descriptions';
|
||||
|
||||
export const description: INodeTypeDescription = {
|
||||
displayName: 'Microsoft Outlook',
|
||||
@@ -31,7 +31,7 @@ export const description: INodeTypeDescription = {
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: sendAndWaitWebhooks,
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import {
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
SEND_AND_WAIT_OPERATION,
|
||||
WAIT_INDEFINITELY,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeOperationError, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||
|
||||
import * as calendar from './calendar';
|
||||
import * as contact from './contact';
|
||||
@@ -15,6 +10,7 @@ import * as folderMessage from './folderMessage';
|
||||
import * as message from './message';
|
||||
import * as messageAttachment from './messageAttachment';
|
||||
import type { MicrosoftOutlook } from './node.type';
|
||||
import { configureWaitTillDate } from '../../../../../utils/sendAndWait/utils';
|
||||
|
||||
export async function router(this: IExecuteFunctions) {
|
||||
const items = this.getInputData();
|
||||
@@ -36,7 +32,9 @@ export async function router(this: IExecuteFunctions) {
|
||||
) {
|
||||
await message[microsoftOutlook.operation].execute.call(this, 0, items);
|
||||
|
||||
await this.putExecutionToWait(WAIT_INDEFINITELY);
|
||||
const waitTill = configureWaitTillDate(this);
|
||||
|
||||
await this.putExecutionToWait(waitTill);
|
||||
return [items];
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import { reactionFields, reactionOperations } from './ReactionDescription';
|
||||
import { starFields, starOperations } from './StarDescription';
|
||||
import { userFields, userOperations } from './UserDescription';
|
||||
import { userGroupFields, userGroupOperations } from './UserGroupDescription';
|
||||
import { sendAndWaitWebhooks } from '../../../utils/sendAndWait/descriptions';
|
||||
import { sendAndWaitWebhooksDescription } from '../../../utils/sendAndWait/descriptions';
|
||||
import {
|
||||
configureWaitTillDate,
|
||||
getSendAndWaitProperties,
|
||||
@@ -81,7 +81,7 @@ export class SlackV2 implements INodeType {
|
||||
},
|
||||
},
|
||||
],
|
||||
webhooks: sendAndWaitWebhooks,
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
getPropertyName,
|
||||
} from './GenericFunctions';
|
||||
import { appendAttributionOption } from '../../utils/descriptions';
|
||||
import { sendAndWaitWebhooks } from '../../utils/sendAndWait/descriptions';
|
||||
import { sendAndWaitWebhooksDescription } from '../../utils/sendAndWait/descriptions';
|
||||
import {
|
||||
configureWaitTillDate,
|
||||
getSendAndWaitProperties,
|
||||
@@ -49,7 +49,7 @@ export class Telegram implements INodeType {
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: sendAndWaitWebhooks,
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IWebhookDescription } from 'n8n-workflow';
|
||||
|
||||
export const sendAndWaitWebhooks: IWebhookDescription[] = [
|
||||
export const sendAndWaitWebhooksDescription: IWebhookDescription[] = [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'GET',
|
||||
|
||||
Reference in New Issue
Block a user