first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,477 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const emailOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Send',
|
||||
value: 'send',
|
||||
description: 'Send a email',
|
||||
action: 'Send an email',
|
||||
},
|
||||
{
|
||||
name: 'Send Template',
|
||||
value: 'sendTemplate',
|
||||
description: 'Send a email template',
|
||||
action: 'Send an email template',
|
||||
},
|
||||
],
|
||||
default: 'send',
|
||||
},
|
||||
];
|
||||
|
||||
export const emailFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* email:send */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'From Email',
|
||||
name: 'fromEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
placeholder: 'admin@example.com',
|
||||
description: 'The title for the email',
|
||||
},
|
||||
{
|
||||
displayName: 'To Email',
|
||||
name: 'toEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'info@example.com',
|
||||
description: 'Email address of the recipient. Multiple ones can be separated by comma.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'My subject line',
|
||||
description: 'Subject line of the email',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Plain text message of email',
|
||||
},
|
||||
{
|
||||
displayName: 'HTML',
|
||||
name: 'html',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'HTML text message of email',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Bcc Email',
|
||||
name: 'bccEmail',
|
||||
type: 'string',
|
||||
description: 'Bcc Email address of the recipient. Multiple ones can be separated by comma.',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Cc Addresses',
|
||||
name: 'ccAddresses',
|
||||
type: 'string',
|
||||
description: 'Cc Email address of the recipient. Multiple ones can be separated by comma.',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'From Name',
|
||||
name: 'fromName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Priority',
|
||||
name: 'priority',
|
||||
type: 'number',
|
||||
default: 2,
|
||||
},
|
||||
{
|
||||
displayName: 'Reply To',
|
||||
name: 'replyTo',
|
||||
type: 'string',
|
||||
description: 'The reply-to email address. Multiple ones can be separated by comma.',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Template Language',
|
||||
name: 'templateLanguage',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Track Clicks',
|
||||
name: 'trackClicks',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Account Default',
|
||||
value: 'account_default',
|
||||
description: 'Use the values specified in the Mailjet account',
|
||||
},
|
||||
{
|
||||
name: 'Disabled',
|
||||
value: 'disabled',
|
||||
description: 'Disable tracking for this message',
|
||||
},
|
||||
{
|
||||
name: 'Enabled',
|
||||
value: 'enabled',
|
||||
description: 'Enable tracking for this message',
|
||||
},
|
||||
],
|
||||
description: 'Enable or disable open tracking on this message',
|
||||
default: 'account_default',
|
||||
},
|
||||
{
|
||||
displayName: 'Track Opens',
|
||||
name: 'trackOpens',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Account Default',
|
||||
value: 'account_default',
|
||||
description: 'Use the values specified in the Mailjet account',
|
||||
},
|
||||
{
|
||||
name: 'Disabled',
|
||||
value: 'disabled',
|
||||
description: 'Disable tracking for this message',
|
||||
},
|
||||
{
|
||||
name: 'Enabled',
|
||||
value: 'enabled',
|
||||
description: 'Enable tracking for this message',
|
||||
},
|
||||
],
|
||||
description: 'Enable or disable open tracking on this message',
|
||||
default: 'account_default',
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Campaign',
|
||||
name: 'customCampaign',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Deduplicate Campaign',
|
||||
name: 'deduplicateCampaign',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Variables (JSON)',
|
||||
name: 'variablesJson',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
jsonParameters: [true],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'HTML text message of email',
|
||||
},
|
||||
{
|
||||
displayName: 'Variables',
|
||||
name: 'variablesUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['send'],
|
||||
jsonParameters: [false],
|
||||
},
|
||||
},
|
||||
placeholder: 'Add Variable',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'variablesValues',
|
||||
displayName: 'Variable',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* email:sendTemplate */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'From Email',
|
||||
name: 'fromEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
},
|
||||
},
|
||||
placeholder: 'admin@example.com',
|
||||
description: 'The title for the email',
|
||||
},
|
||||
{
|
||||
displayName: 'To Email',
|
||||
name: 'toEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'info@example.com',
|
||||
description: 'Email address of the recipient. Multiple ones can be separated by comma.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Template Name or ID',
|
||||
name: 'templateId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTemplates',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Bcc Email',
|
||||
name: 'bccEmail',
|
||||
type: 'string',
|
||||
description: 'BCC Recipients of the email separated by ,',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Cc Email',
|
||||
name: 'ccEmail',
|
||||
type: 'string',
|
||||
description: 'Cc recipients of the email separated by ,',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'From Name',
|
||||
name: 'fromName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Priority',
|
||||
name: 'priority',
|
||||
type: 'number',
|
||||
default: 2,
|
||||
},
|
||||
{
|
||||
displayName: 'Reply To',
|
||||
name: 'replyTo',
|
||||
type: 'string',
|
||||
description: 'The reply-to email address. Multiple ones can be separated by comma.',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Template Language',
|
||||
name: 'templateLanguage',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Track Clicks',
|
||||
name: 'trackClicks',
|
||||
type: 'string',
|
||||
description: 'Enable or disable open tracking on this message',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Track Opens',
|
||||
name: 'trackOpens',
|
||||
type: 'string',
|
||||
description: 'Enable or disable open tracking on this message',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Campaign',
|
||||
name: 'customCampaign',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Deduplicate Campaign',
|
||||
name: 'deduplicateCampaign',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Variables',
|
||||
name: 'variablesUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
jsonParameters: [false],
|
||||
},
|
||||
},
|
||||
placeholder: 'Add Variable',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'variablesValues',
|
||||
displayName: 'Variable',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Variables (JSON)',
|
||||
name: 'variablesJson',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
operation: ['sendTemplate'],
|
||||
jsonParameters: [true],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'HTML text message of email',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,108 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
IHttpRequestMethods,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function mailjetApiRequest(
|
||||
this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
|
||||
body: any = {},
|
||||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
option: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
|
||||
let credentialType;
|
||||
|
||||
if (resource === 'email' || this.getNode().type.includes('Trigger')) {
|
||||
credentialType = 'mailjetEmailApi';
|
||||
const { sandboxMode } = await this.getCredentials<{
|
||||
sandboxMode: boolean;
|
||||
}>('mailjetEmailApi');
|
||||
|
||||
if (!this.getNode().type.includes('Trigger')) {
|
||||
Object.assign(body, { SandboxMode: sandboxMode });
|
||||
}
|
||||
} else {
|
||||
credentialType = 'mailjetSmsApi';
|
||||
}
|
||||
|
||||
let options: IRequestOptions = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri || `https://api.mailjet.com${path}`,
|
||||
json: true,
|
||||
};
|
||||
options = Object.assign({}, options, option);
|
||||
if (Object.keys(options.body as IDataObject).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
return await this.helpers.requestWithAuthentication.call(this, credentialType, options);
|
||||
}
|
||||
|
||||
export async function mailjetApiRequestAllItems(
|
||||
this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
query.Limit = 1000;
|
||||
query.Offset = 0;
|
||||
|
||||
do {
|
||||
responseData = await mailjetApiRequest.call(this, method, endpoint, body, query, undefined, {
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
returnData.push.apply(returnData, responseData.body as IDataObject[]);
|
||||
query.Offset = query.Offset + query.Limit;
|
||||
} while (responseData.length !== 0);
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function validateJSON(json: string | undefined): IDataObject | undefined {
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(json!);
|
||||
} catch (exception) {
|
||||
result = undefined;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface IMessage {
|
||||
From?: { Email?: string; Name?: string };
|
||||
Subject?: string;
|
||||
To?: IDataObject[];
|
||||
Cc?: IDataObject[];
|
||||
Bcc?: IDataObject[];
|
||||
Variables?: IDataObject;
|
||||
TemplateLanguage?: boolean;
|
||||
TemplateID?: number;
|
||||
HTMLPart?: string;
|
||||
TextPart?: string;
|
||||
TrackOpens?: string;
|
||||
ReplyTo?: IDataObject;
|
||||
TrackClicks?: string;
|
||||
Priority?: number;
|
||||
CustomCampaign?: string;
|
||||
DeduplicateCampaign?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.mailjet",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication", "Development"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/mailjet/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.mailjet/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { emailFields, emailOperations } from './EmailDescription';
|
||||
import type { IMessage } from './GenericFunctions';
|
||||
import { mailjetApiRequest, validateJSON } from './GenericFunctions';
|
||||
import { smsFields, smsOperations } from './SmsDescription';
|
||||
export class Mailjet implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Mailjet',
|
||||
name: 'mailjet',
|
||||
icon: 'file:mailjet.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Mailjet API',
|
||||
defaults: {
|
||||
name: 'Mailjet',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'mailjetEmailApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['email'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mailjetSmsApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['sms'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Email',
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
name: 'SMS',
|
||||
value: 'sms',
|
||||
},
|
||||
],
|
||||
default: 'email',
|
||||
},
|
||||
...emailOperations,
|
||||
...emailFields,
|
||||
...smsOperations,
|
||||
...smsFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available custom fields to display them to user so that they can
|
||||
// select them easily
|
||||
async getTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { Data: templates } = await mailjetApiRequest.call(this, 'GET', '/v3/REST/template');
|
||||
for (const template of templates) {
|
||||
returnData.push({
|
||||
name: template.Name,
|
||||
value: template.ID,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const length = items.length;
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
try {
|
||||
if (resource === 'email') {
|
||||
//https://dev.mailjet.com/email/guides/send-api-v31/#send-a-basic-email
|
||||
if (operation === 'send') {
|
||||
const fromEmail = this.getNodeParameter('fromEmail', i) as string;
|
||||
const htmlBody = this.getNodeParameter('html', i) as string;
|
||||
const textBody = this.getNodeParameter('text', i) as string;
|
||||
const subject = this.getNodeParameter('subject', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const toEmail = (this.getNodeParameter('toEmail', i) as string).split(',');
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i);
|
||||
|
||||
const body: IMessage = {
|
||||
From: {
|
||||
Email: fromEmail,
|
||||
},
|
||||
Subject: subject,
|
||||
To: [],
|
||||
Cc: [],
|
||||
Bcc: [],
|
||||
Variables: {},
|
||||
};
|
||||
|
||||
for (const email of toEmail) {
|
||||
body.To?.push({
|
||||
Email: email,
|
||||
});
|
||||
}
|
||||
|
||||
if (jsonParameters) {
|
||||
const variablesJson = this.getNodeParameter('variablesJson', i) as string;
|
||||
const parsedJson = validateJSON(variablesJson);
|
||||
if (parsedJson === undefined) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"Parameter 'Variables (JSON)' has a invalid JSON",
|
||||
{ itemIndex: i },
|
||||
);
|
||||
}
|
||||
body.Variables = parsedJson;
|
||||
} else {
|
||||
const variables =
|
||||
((this.getNodeParameter('variablesUi', i) as IDataObject)
|
||||
.variablesValues as IDataObject[]) || [];
|
||||
for (const variable of variables) {
|
||||
body.Variables![variable.name as string] = variable.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (htmlBody) {
|
||||
body.HTMLPart = htmlBody;
|
||||
}
|
||||
if (textBody) {
|
||||
body.TextPart = textBody;
|
||||
}
|
||||
if (additionalFields.bccEmail) {
|
||||
const bccEmail = (additionalFields.bccEmail as string).split(',');
|
||||
for (const email of bccEmail) {
|
||||
body.Bcc!.push({
|
||||
Email: email,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (additionalFields.ccAddresses) {
|
||||
const ccEmail = (additionalFields.ccAddresses as string).split(',');
|
||||
for (const email of ccEmail) {
|
||||
body.Cc!.push({
|
||||
Email: email,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (additionalFields.trackOpens) {
|
||||
body.TrackOpens = additionalFields.trackOpens as string;
|
||||
}
|
||||
if (additionalFields.replyTo) {
|
||||
const replyTo = additionalFields.replyTo as string;
|
||||
body.ReplyTo = {
|
||||
Email: replyTo,
|
||||
};
|
||||
}
|
||||
if (additionalFields.trackClicks) {
|
||||
body.TrackClicks = additionalFields.trackClicks as string;
|
||||
}
|
||||
if (additionalFields.fromName) {
|
||||
body.From!.Name = additionalFields.fromName as string;
|
||||
}
|
||||
if (additionalFields.templateLanguage) {
|
||||
body.TemplateLanguage = additionalFields.templateLanguage as boolean;
|
||||
}
|
||||
if (additionalFields.priority) {
|
||||
body.Priority = additionalFields.priority as number;
|
||||
}
|
||||
if (additionalFields.customCampaign) {
|
||||
body.CustomCampaign = additionalFields.customCampaign as string;
|
||||
}
|
||||
if (additionalFields.deduplicateCampaign) {
|
||||
body.DeduplicateCampaign = additionalFields.deduplicateCampaign as boolean;
|
||||
}
|
||||
responseData = await mailjetApiRequest.call(this, 'POST', '/v3.1/send', {
|
||||
Messages: [body],
|
||||
});
|
||||
responseData = responseData.Messages;
|
||||
}
|
||||
//https://dev.mailjet.com/email/guides/send-api-v31/#use-a-template
|
||||
if (operation === 'sendTemplate') {
|
||||
const fromEmail = this.getNodeParameter('fromEmail', i) as string;
|
||||
const templateId = parseInt(this.getNodeParameter('templateId', i) as string, 10);
|
||||
const subject = this.getNodeParameter('subject', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const toEmail = (this.getNodeParameter('toEmail', i) as string).split(',');
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i);
|
||||
|
||||
const body: IMessage = {
|
||||
From: {
|
||||
Email: fromEmail,
|
||||
},
|
||||
Subject: subject,
|
||||
To: [],
|
||||
Cc: [],
|
||||
Bcc: [],
|
||||
Variables: {},
|
||||
TemplateID: templateId,
|
||||
};
|
||||
|
||||
for (const email of toEmail) {
|
||||
body.To!.push({
|
||||
Email: email,
|
||||
});
|
||||
}
|
||||
|
||||
if (jsonParameters) {
|
||||
const variablesJson = this.getNodeParameter('variablesJson', i) as string;
|
||||
const parsedJson = validateJSON(variablesJson);
|
||||
if (parsedJson === undefined) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"Parameter 'Variables (JSON)' has a invalid JSON",
|
||||
{ itemIndex: i },
|
||||
);
|
||||
}
|
||||
body.Variables = parsedJson;
|
||||
} else {
|
||||
const variables =
|
||||
((this.getNodeParameter('variablesUi', i) as IDataObject)
|
||||
.variablesValues as IDataObject[]) || [];
|
||||
for (const variable of variables) {
|
||||
body.Variables![variable.name as string] = variable.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.bccEmail) {
|
||||
const bccEmail = (additionalFields.bccEmail as string).split(',');
|
||||
for (const email of bccEmail) {
|
||||
body.Bcc!.push({
|
||||
Email: email,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (additionalFields.ccEmail) {
|
||||
const ccEmail = (additionalFields.ccEmail as string).split(',');
|
||||
for (const email of ccEmail) {
|
||||
body.Cc!.push({
|
||||
Email: email,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (additionalFields.replyTo) {
|
||||
const replyTo = additionalFields.replyTo as string;
|
||||
body.ReplyTo = {
|
||||
Email: replyTo,
|
||||
};
|
||||
}
|
||||
if (additionalFields.trackOpens) {
|
||||
body.TrackOpens = additionalFields.trackOpens as string;
|
||||
}
|
||||
if (additionalFields.trackClicks) {
|
||||
body.TrackClicks = additionalFields.trackClicks as string;
|
||||
}
|
||||
if (additionalFields.fromName) {
|
||||
body.From!.Name = additionalFields.fromName as string;
|
||||
}
|
||||
if (additionalFields.templateLanguage) {
|
||||
body.TemplateLanguage = additionalFields.templateLanguage as boolean;
|
||||
}
|
||||
if (additionalFields.priority) {
|
||||
body.Priority = additionalFields.priority as number;
|
||||
}
|
||||
if (additionalFields.customCampaign) {
|
||||
body.CustomCampaign = additionalFields.customCampaign as string;
|
||||
}
|
||||
if (additionalFields.deduplicateCampaign) {
|
||||
body.DeduplicateCampaign = additionalFields.deduplicateCampaign as boolean;
|
||||
}
|
||||
responseData = await mailjetApiRequest.call(this, 'POST', '/v3.1/send', {
|
||||
Messages: [body],
|
||||
});
|
||||
responseData = responseData.Messages;
|
||||
}
|
||||
}
|
||||
if (resource === 'sms') {
|
||||
//https://dev.mailjet.com/sms/reference/send-message#v4_post_sms-send
|
||||
if (operation === 'send') {
|
||||
const from = this.getNodeParameter('from', i) as string;
|
||||
const to = this.getNodeParameter('to', i) as boolean;
|
||||
const text = this.getNodeParameter('text', i) as string;
|
||||
const body: IDataObject = {
|
||||
From: from,
|
||||
To: to,
|
||||
Text: text,
|
||||
};
|
||||
responseData = await mailjetApiRequest.call(this, 'POST', '/v4/sms-send', body);
|
||||
}
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.mailjetTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication", "Development"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/mailjet/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.mailjettrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type {
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
IDataObject,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import { mailjetApiRequest } from './GenericFunctions';
|
||||
|
||||
export class MailjetTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Mailjet Trigger',
|
||||
name: 'mailjetTrigger',
|
||||
icon: 'file:mailjet.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Handle Mailjet events via webhooks',
|
||||
defaults: {
|
||||
name: 'Mailjet Trigger',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'mailjetEmailApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Event',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'open',
|
||||
options: [
|
||||
{
|
||||
name: 'email.blocked',
|
||||
value: 'blocked',
|
||||
},
|
||||
{
|
||||
name: 'email.bounce',
|
||||
value: 'bounce',
|
||||
},
|
||||
{
|
||||
name: 'email.open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'email.sent',
|
||||
value: 'sent',
|
||||
},
|
||||
{
|
||||
name: 'email.spam',
|
||||
value: 'spam',
|
||||
},
|
||||
{
|
||||
name: 'email.unsub',
|
||||
value: 'unsub',
|
||||
},
|
||||
],
|
||||
description: 'Determines which resource events the webhook is triggered for',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const endpoint = '/v3/rest/eventcallbackurl';
|
||||
const responseData = await mailjetApiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
|
||||
for (const webhook of responseData.Data) {
|
||||
if (webhook.EventType === event && webhook.Url === webhookUrl) {
|
||||
// Set webhook-id to be sure that it can be deleted
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
webhookData.webhookId = webhook.ID as string;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const endpoint = '/v3/rest/eventcallbackurl';
|
||||
const body: IDataObject = {
|
||||
Url: webhookUrl,
|
||||
EventType: event,
|
||||
Status: 'alive',
|
||||
isBackup: 'false',
|
||||
};
|
||||
const { Data } = await mailjetApiRequest.call(this, 'POST', endpoint, body);
|
||||
webhookData.webhookId = Data[0].ID;
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const endpoint = `/v3/rest/eventcallbackurl/${webhookData.webhookId}`;
|
||||
try {
|
||||
await mailjetApiRequest.call(this, 'DELETE', endpoint);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
delete webhookData.webhookId;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const req = this.getRequestObject();
|
||||
return {
|
||||
workflowData: [this.helpers.returnJsonArray(req.body as IDataObject[])],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const smsOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['sms'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Send',
|
||||
value: 'send',
|
||||
description: 'Send a sms',
|
||||
action: 'Send an SMS',
|
||||
},
|
||||
],
|
||||
default: 'send',
|
||||
},
|
||||
];
|
||||
|
||||
export const smsFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* sms:send */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'From',
|
||||
name: 'from',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['sms'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'Customizable sender name. Should be between 3 and 11 characters in length, only alphanumeric characters are allowed.',
|
||||
},
|
||||
{
|
||||
displayName: 'To',
|
||||
name: 'to',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['sms'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'Message recipient. Should be between 3 and 15 characters in length. The number always starts with a plus sign followed by a country code, followed by the number. Phone numbers are expected to comply with the E.164 format.',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['sms'],
|
||||
operation: ['send'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Bcc": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Email": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageHref": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"MessageUUID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cc": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Email": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageHref": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"MessageUUID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CustomID": {
|
||||
"type": "string"
|
||||
},
|
||||
"Status": {
|
||||
"type": "string"
|
||||
},
|
||||
"To": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Email": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageHref": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"MessageUUID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Bcc": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Email": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageHref": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"MessageUUID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cc": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Email": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageHref": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"MessageUUID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CustomID": {
|
||||
"type": "string"
|
||||
},
|
||||
"Status": {
|
||||
"type": "string"
|
||||
},
|
||||
"To": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Email": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageHref": {
|
||||
"type": "string"
|
||||
},
|
||||
"MessageID": {
|
||||
"type": "integer"
|
||||
},
|
||||
"MessageUUID": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0.5 90 402.6 222.2"><path fill="#fead0c" d="m129.1 195.5-7 46.5-48 70.2 16.2-8.9L354 158.1l49.1-27zm-1.7-12.3 206.8-47-13.4-1.8-54.3-7.6-115.2-15.9L.5 90l55.3 40.6 69.5 51.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 259 B |
@@ -0,0 +1,602 @@
|
||||
import { mock, mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
import { Mailjet } from '../Mailjet.node';
|
||||
import * as GenericFunctions from '../GenericFunctions';
|
||||
|
||||
describe('Mailjet Node', () => {
|
||||
let mailjetNode: Mailjet;
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
const mailjetApiRequestSpy = jest.spyOn(GenericFunctions, 'mailjetApiRequest');
|
||||
|
||||
beforeEach(() => {
|
||||
mailjetNode = new Mailjet();
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>({
|
||||
helpers: {
|
||||
constructExecutionMetaData: jest.fn().mockImplementation((data) => data),
|
||||
returnJsonArray: jest
|
||||
.fn()
|
||||
.mockImplementation((data) =>
|
||||
Array.isArray(data) ? data.map((item) => ({ json: item })) : [{ json: data }],
|
||||
),
|
||||
},
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
|
||||
mockExecuteFunctions.getNode.mockReturnValue(
|
||||
mock<INode>({
|
||||
id: 'test-mailjet-node',
|
||||
name: 'Mailjet Test',
|
||||
type: 'n8n-nodes-base.mailjet',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
}),
|
||||
);
|
||||
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Email Resource - Send Operation', () => {
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string, _: number) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
additionalFields: {},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
mailjetApiRequestSpy.mockResolvedValue({
|
||||
Messages: [
|
||||
{
|
||||
Status: 'success',
|
||||
To: [{ Email: 'recipient@example.com', MessageID: 123456789 }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should send email with customCampaign field', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'my-custom-campaign',
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
CustomCampaign: 'my-custom-campaign',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send email with deduplicateCampaign field set to true', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
deduplicateCampaign: true,
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
DeduplicateCampaign: true,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not include deduplicateCampaign field when set to false', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
deduplicateCampaign: false,
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
DeduplicateCampaign: expect.anything(),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send email with both customCampaign and deduplicateCampaign fields', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'my-campaign-2024',
|
||||
deduplicateCampaign: true,
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
CustomCampaign: 'my-campaign-2024',
|
||||
DeduplicateCampaign: true,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send email without customCampaign and deduplicateCampaign when not provided', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
CustomCampaign: expect.anything(),
|
||||
DeduplicateCampaign: expect.anything(),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send email with other additional fields alongside customCampaign', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test HTML Body</p>',
|
||||
text: 'Test Text Body',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
fromName: 'Test Sender',
|
||||
priority: 3,
|
||||
customCampaign: 'multi-field-campaign',
|
||||
trackOpens: 'enabled',
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
From: expect.objectContaining({
|
||||
Name: 'Test Sender',
|
||||
}),
|
||||
Priority: 3,
|
||||
CustomCampaign: 'multi-field-campaign',
|
||||
TrackOpens: 'enabled',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Email Resource - Send Template Operation', () => {
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'sendTemplate',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
templateId: '12345',
|
||||
subject: 'Test Template Email',
|
||||
jsonParameters: false,
|
||||
additionalFields: {},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
mailjetApiRequestSpy.mockResolvedValue({
|
||||
Messages: [
|
||||
{
|
||||
Status: 'success',
|
||||
To: [{ Email: 'recipient@example.com', MessageID: 987654321 }],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should send template email with customCampaign field', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'sendTemplate',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
templateId: '12345',
|
||||
subject: 'Test Template Email',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'template-campaign',
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
TemplateID: 12345,
|
||||
CustomCampaign: 'template-campaign',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send template email with deduplicateCampaign field set to true', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'sendTemplate',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
templateId: '12345',
|
||||
subject: 'Test Template Email',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
deduplicateCampaign: true,
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
TemplateID: 12345,
|
||||
DeduplicateCampaign: true,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send template email with customCampaign but not include deduplicateCampaign when false', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'sendTemplate',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
templateId: '12345',
|
||||
subject: 'Test Template Email',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'template-campaign-v2',
|
||||
deduplicateCampaign: false,
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
TemplateID: 12345,
|
||||
CustomCampaign: 'template-campaign-v2',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
const calls = mailjetApiRequestSpy.mock.calls;
|
||||
const lastCall = calls[calls.length - 1];
|
||||
expect(lastCall[2].Messages[0]).not.toHaveProperty('DeduplicateCampaign');
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should send template email without customCampaign and deduplicateCampaign when not provided', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'sendTemplate',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
templateId: '12345',
|
||||
subject: 'Test Template Email',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
CustomCampaign: expect.anything(),
|
||||
DeduplicateCampaign: expect.anything(),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Items Processing', () => {
|
||||
it('should handle multiple items with different customCampaign values', async () => {
|
||||
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }, { json: {} }]);
|
||||
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation(
|
||||
(paramName: string, index: number) => {
|
||||
const paramsForIndex: Array<Record<string, any>> = [
|
||||
{
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient1@example.com',
|
||||
subject: 'Test Email 1',
|
||||
html: '<p>Test 1</p>',
|
||||
text: 'Test 1',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'campaign-1',
|
||||
deduplicateCampaign: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient2@example.com',
|
||||
subject: 'Test Email 2',
|
||||
html: '<p>Test 2</p>',
|
||||
text: 'Test 2',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'campaign-2',
|
||||
deduplicateCampaign: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
return paramsForIndex[index][paramName];
|
||||
},
|
||||
);
|
||||
|
||||
mailjetApiRequestSpy.mockResolvedValue({
|
||||
Messages: [{ Status: 'success' }],
|
||||
});
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(mailjetApiRequestSpy).toHaveBeenCalledTimes(2);
|
||||
expect(mailjetApiRequestSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
CustomCampaign: 'campaign-1',
|
||||
DeduplicateCampaign: true,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(mailjetApiRequestSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'POST',
|
||||
'/v3.1/send',
|
||||
expect.objectContaining({
|
||||
Messages: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
CustomCampaign: 'campaign-2',
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
const secondCall = mailjetApiRequestSpy.mock.calls[1];
|
||||
expect(secondCall[2].Messages[0]).not.toHaveProperty('DeduplicateCampaign');
|
||||
expect(result[0]).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle API errors gracefully when continueOnFail is true', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test</p>',
|
||||
text: 'Test',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
customCampaign: 'error-campaign',
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
mockExecuteFunctions.continueOnFail.mockReturnValue(true);
|
||||
mailjetApiRequestSpy.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
const result = await mailjetNode.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0][0].json).toHaveProperty('error', 'API Error');
|
||||
});
|
||||
|
||||
it('should throw error when continueOnFail is false', async () => {
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((paramName: string) => {
|
||||
const params: { [key: string]: any } = {
|
||||
resource: 'email',
|
||||
operation: 'send',
|
||||
fromEmail: 'sender@example.com',
|
||||
toEmail: 'recipient@example.com',
|
||||
subject: 'Test Email',
|
||||
html: '<p>Test</p>',
|
||||
text: 'Test',
|
||||
jsonParameters: false,
|
||||
variablesUi: { variablesValues: [] },
|
||||
additionalFields: {
|
||||
deduplicateCampaign: true,
|
||||
},
|
||||
};
|
||||
return params[paramName];
|
||||
});
|
||||
|
||||
mockExecuteFunctions.continueOnFail.mockReturnValue(false);
|
||||
mailjetApiRequestSpy.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
await expect(mailjetNode.execute.call(mockExecuteFunctions)).rejects.toThrow('API Error');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user