feat(MailerLite Node): Update node to support new api (#11933)

This commit is contained in:
Jon 2024-12-16 13:44:52 +00:00 committed by GitHub
parent 271401d882
commit d6b8e65abe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 2830 additions and 394 deletions

View file

@ -1,4 +1,10 @@
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
import type {
ICredentialDataDecryptedObject,
ICredentialTestRequest,
ICredentialType,
IHttpRequestOptions,
INodeProperties,
} from 'n8n-workflow';
export class MailerLiteApi implements ICredentialType {
name = 'mailerLiteApi';
@ -15,5 +21,37 @@ export class MailerLiteApi implements ICredentialType {
typeOptions: { password: true },
default: '',
},
{
displayName: 'Classic API',
name: 'classicApi',
type: 'boolean',
default: true,
description:
'If the Classic API should be used, If this is your first time using this node this should be false.',
},
];
async authenticate(
credentials: ICredentialDataDecryptedObject,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
if (credentials.classicApi === true) {
requestOptions.headers = {
'X-MailerLite-ApiKey': credentials.apiKey as string,
};
} else {
requestOptions.headers = {
Authorization: `Bearer ${credentials.apiKey as string}`,
};
}
return requestOptions;
}
test: ICredentialTestRequest = {
request: {
baseURL:
'={{$credentials.classicApi ? "https://api.mailerlite.com/api/v2" : "https://connect.mailerlite.com/api"}}',
url: '/groups',
},
};
}

View file

@ -3,39 +3,38 @@ import type {
IExecuteFunctions,
IHookFunctions,
ILoadOptionsFunctions,
INodePropertyOptions,
JsonObject,
IRequestOptions,
IHttpRequestOptions,
IHttpRequestMethods,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import type { CustomField } from './v2/MailerLite.Interface';
export async function mailerliteApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions,
method: IHttpRequestMethods,
path: string,
body: any = {},
qs: IDataObject = {},
_option = {},
): Promise<any> {
const credentials = await this.getCredentials('mailerLiteApi');
const options: IRequestOptions = {
headers: {
'X-MailerLite-ApiKey': credentials.apiKey,
},
const options: IHttpRequestOptions = {
method,
body,
qs,
uri: `https://api.mailerlite.com/api/v2${path}`,
url:
this.getNode().typeVersion === 1
? `https://api.mailerlite.com/api/v2${path}`
: `https://connect.mailerlite.com/api${path}`,
json: true,
};
try {
if (Object.keys(body as IDataObject).length === 0) {
delete options.body;
}
//@ts-ignore
return await this.helpers.request.call(this, options);
return await this.helpers.httpRequestWithAuthentication.call(this, 'mailerLiteApi', options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
@ -45,21 +44,56 @@ export async function mailerliteApiRequestAllItems(
this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: any = {},
query: IDataObject = {},
): Promise<any> {
const returnData: IDataObject[] = [];
let responseData;
query.limit = 1000;
query.offset = 0;
do {
responseData = await mailerliteApiRequest.call(this, method, endpoint, body, query);
returnData.push.apply(returnData, responseData as IDataObject[]);
query.offset = query.offset + query.limit;
} while (responseData.length !== 0);
if (this.getNode().typeVersion === 1) {
do {
responseData = await mailerliteApiRequest.call(this, method, endpoint, body, query);
returnData.push(...(responseData as IDataObject[]));
query.offset += query.limit;
} while (responseData.length !== 0);
} else {
do {
responseData = await mailerliteApiRequest.call(this, method, endpoint, body, query);
returnData.push(...(responseData.data as IDataObject[]));
query.cursor = responseData.meta.next_cursor;
} while (responseData.links.next !== null);
}
return returnData;
}
export async function getCustomFields(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const endpoint = '/fields';
const fieldsResponse = await mailerliteApiRequest.call(this, 'GET', endpoint);
if (this.getNode().typeVersion === 1) {
const fields = fieldsResponse as CustomField[];
fields.forEach((field) => {
returnData.push({
name: field.key,
value: field.key,
});
});
} else {
const fields = (fieldsResponse as IDataObject).data as CustomField[];
fields.forEach((field) => {
returnData.push({
name: field.name,
value: field.key,
});
});
}
return returnData;
}

View file

@ -1,206 +1,26 @@
import type {
IExecuteFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
import { VersionedNodeType } from 'n8n-workflow';
import { mailerliteApiRequest, mailerliteApiRequestAllItems } from './GenericFunctions';
import { MailerLiteV1 } from './v1/MailerLiteV1.node';
import { MailerLiteV2 } from './v2/MailerLiteV2.node';
import { subscriberFields, subscriberOperations } from './SubscriberDescription';
export class MailerLite extends VersionedNodeType {
constructor() {
const baseDescription: INodeTypeBaseDescription = {
displayName: 'MailerLite',
name: 'mailerLite',
icon: 'file:MailerLite.svg',
group: ['input'],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume MailerLite API',
defaultVersion: 2,
};
export class MailerLite implements INodeType {
description: INodeTypeDescription = {
displayName: 'MailerLite',
name: 'mailerLite',
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
icon: 'file:mailerLite.png',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Mailer Lite API',
defaults: {
name: 'MailerLite',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'mailerLiteApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Subscriber',
value: 'subscriber',
},
],
default: 'subscriber',
},
...subscriberOperations,
...subscriberFields,
],
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new MailerLiteV1(baseDescription),
2: new MailerLiteV2(baseDescription),
};
methods = {
loadOptions: {
// Get all the available custom fields to display them to user so that they can
// select them easily
async getCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const fields = await mailerliteApiRequest.call(this, 'GET', '/fields');
for (const field of fields) {
returnData.push({
name: field.key,
value: field.key,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const length = items.length;
const qs: IDataObject = {};
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
try {
if (resource === 'subscriber') {
//https://developers.mailerlite.com/reference#create-a-subscriber
if (operation === 'create') {
const email = this.getNodeParameter('email', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IDataObject = {
email,
fields: [],
};
Object.assign(body, additionalFields);
if (additionalFields.customFieldsUi) {
const customFieldsValues = (additionalFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[];
if (customFieldsValues) {
const fields = {};
for (const customFieldValue of customFieldsValues) {
//@ts-ignore
fields[customFieldValue.fieldId] = customFieldValue.value;
}
body.fields = fields;
delete body.customFieldsUi;
}
}
responseData = await mailerliteApiRequest.call(this, 'POST', '/subscribers', body);
}
//https://developers.mailerlite.com/reference#single-subscriber
if (operation === 'get') {
const subscriberId = this.getNodeParameter('subscriberId', i) as string;
responseData = await mailerliteApiRequest.call(
this,
'GET',
`/subscribers/${subscriberId}`,
);
}
//https://developers.mailerlite.com/reference#subscribers
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
const filters = this.getNodeParameter('filters', i);
Object.assign(qs, filters);
if (returnAll) {
responseData = await mailerliteApiRequestAllItems.call(
this,
'GET',
'/subscribers',
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await mailerliteApiRequest.call(this, 'GET', '/subscribers', {}, qs);
}
}
//https://developers.mailerlite.com/reference#update-subscriber
if (operation === 'update') {
const subscriberId = this.getNodeParameter('subscriberId', i) as string;
const updateFields = this.getNodeParameter('updateFields', i);
const body: IDataObject = {};
Object.assign(body, updateFields);
if (updateFields.customFieldsUi) {
const customFieldsValues = (updateFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[];
if (customFieldsValues) {
const fields = {};
for (const customFieldValue of customFieldsValues) {
//@ts-ignore
fields[customFieldValue.fieldId] = customFieldValue.value;
}
body.fields = fields;
delete body.customFieldsUi;
}
}
responseData = await mailerliteApiRequest.call(
this,
'PUT',
`/subscribers/${subscriberId}`,
body,
);
}
}
} 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;
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
return [returnData];
super(nodeVersions, baseDescription);
}
}

View file

@ -0,0 +1,33 @@
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 62.8 50.2" style="enable-background:new 0 0 62.8 50.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#09C269;}
.st1{fill:#FFFFFF;}
</style>
<metadata>
<sfw xmlns="ns_sfw;">
<slices>
</slices>
<sliceSourceBounds bottomLeftOrigin="true" height="50.2" width="62.8" x="236.9" y="-225.3">
</sliceSourceBounds>
</sfw>
</metadata>
<g id="mailerlite-light">
<g>
<g id="lite" transform="translate(137.000000, 0.000000)">
<path id="Shape-path" class="st0" d="M-81.2,0h-48.9c-3.8,0-6.9,3.1-6.9,6.8v22.8v4.5v16.2l9.5-9.3h46.4c3.8,0,6.9-3.1,6.9-6.8
V6.8C-74.3,3.1-77.4,0-81.2,0z">
</path>
<path id="Shape-path-3" class="st1" d="M-90.2,15.8c5.2,0,7.6,4.1,7.6,8c0,1-0.8,1.8-1.8,1.8H-94c0.5,2.3,2.1,3.6,4.7,3.6
c1.9,0,2.9-0.4,3.9-0.9c0.2-0.1,0.5-0.2,0.7-0.2c0.9,0,1.7,0.7,1.7,1.6c0,0.6-0.4,1.1-1,1.5c-1.3,0.7-2.7,1.4-5.5,1.4
c-5.2,0-8.3-3.1-8.3-8.4C-97.9,18.1-93.7,15.8-90.2,15.8z M-105.5,13.2c0.6,0,1,0.5,1,1v1.9h2.9c0.9,0,1.7,0.7,1.7,1.6
c0,0.9-0.7,1.6-1.7,1.6h-2.9V28c0,1.2,0.6,1.3,1.5,1.3c0.5,0,0.8-0.1,1.1-0.1c0.2,0,0.5-0.1,0.7-0.1c0.7,0,1.6,0.6,1.6,1.5
c0,0.6-0.4,1.1-1,1.4c-0.9,0.4-1.7,0.6-2.7,0.6c-3.2,0-4.9-1.5-4.9-4.4v-8.8h-1.7c-0.6,0-1-0.5-1-1c0-0.3,0.1-0.6,0.4-0.9l4-4
C-106.3,13.5-106,13.2-105.5,13.2z M-124.2,9.4c1,0,1.8,0.8,1.8,1.8v19.4c0,1-0.8,1.8-1.8,1.8s-1.8-0.8-1.8-1.8V11.2
C-126,10.2-125.2,9.4-124.2,9.4z M-115.6,16c1,0,1.8,0.8,1.8,1.8v12.8c0,1-0.8,1.8-1.8,1.8c-1,0-1.8-0.8-1.8-1.8V17.8
C-117.4,16.8-116.6,16-115.6,16z M-90.1,19.1c-1.7,0-3.6,1-3.9,3.5h7.9C-86.6,20.1-88.4,19.1-90.1,19.1z M-115.5,9.9
c1.1,0,2,0.9,2,2V12c0,1.1-0.9,2-2,2h-0.2c-1.1,0-2-0.9-2-2v-0.1c0-1.1,0.9-2,2-2H-115.5z">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,180 +1,25 @@
import type {
IHookFunctions,
IWebhookFunctions,
IDataObject,
INodeType,
INodeTypeDescription,
IWebhookResponseData,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
import { VersionedNodeType } from 'n8n-workflow';
import { mailerliteApiRequest } from './GenericFunctions';
import { MailerLiteTriggerV1 } from './v1/MailerLiteTriggerV1.node';
import { MailerLiteTriggerV2 } from './v2/MailerLiteTriggerV2.node';
export class MailerLiteTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'MailerLite Trigger',
name: 'mailerLiteTrigger',
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
icon: 'file:mailerLite.png',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when MailerLite events occur',
defaults: {
name: 'MailerLite Trigger',
},
inputs: [],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'mailerLiteApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Event',
name: 'event',
type: 'options',
options: [
{
name: 'Campaign Sent',
value: 'campaign.sent',
description: 'Fired when campaign is sent',
},
{
name: 'Subscriber Added Throught Webform',
value: 'subscriber.added_through_webform',
description: 'Fired when a subscriber is added though a form',
},
{
name: 'Subscriber Added to Group',
value: 'subscriber.add_to_group',
description: 'Fired when a subscriber is added to a group',
},
{
name: 'Subscriber Autonomation Completed',
value: 'subscriber.automation_complete',
description: 'Fired when subscriber finishes automation',
},
{
name: 'Subscriber Autonomation Triggered',
value: 'subscriber.automation_triggered',
description: 'Fired when subscriber starts automation',
},
{
name: 'Subscriber Bounced',
value: 'subscriber.bounced',
description: 'Fired when an email address bounces',
},
{
name: 'Subscriber Complained',
value: 'subscriber.complaint',
description: 'Fired when subscriber marks a campaign as a spam',
},
{
name: 'Subscriber Created',
value: 'subscriber.create',
description: 'Fired when a new subscriber is added to an account',
},
{
name: 'Subscriber Removed From Group',
value: 'subscriber.remove_from_group',
description: 'Fired when a subscriber is removed from a group',
},
{
name: 'Subscriber Unsubscribe',
value: 'subscriber.unsubscribe',
description: 'Fired when a subscriber becomes unsubscribed',
},
{
name: 'Subscriber Updated',
value: 'subscriber.update',
description: "Fired when any of the subscriber's custom fields are updated",
},
],
required: true,
default: [],
description: 'The events to listen to',
},
],
};
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const webhookData = this.getWorkflowStaticData('node');
const event = this.getNodeParameter('event') as string;
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const endpoint = '/webhooks';
const { webhooks } = await mailerliteApiRequest.call(this, 'GET', endpoint, {});
for (const webhook of webhooks) {
if (webhook.url === webhookUrl && webhook.event === event) {
// Set webhook-id to be sure that it can be deleted
webhookData.webhookId = webhook.id as string;
return true;
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const event = this.getNodeParameter('event') as string;
const endpoint = '/webhooks';
const body = {
url: webhookUrl,
event,
};
const responseData = await mailerliteApiRequest.call(this, 'POST', endpoint, body);
if (responseData.id === undefined) {
// Required data is missing so was not successful
return false;
}
webhookData.webhookId = responseData.id as string;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const endpoint = `/webhooks/${webhookData.webhookId}`;
try {
await mailerliteApiRequest.call(this, 'DELETE', endpoint);
} catch (error) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const body = this.getBodyData();
const events = body.events as IDataObject[];
return {
workflowData: [this.helpers.returnJsonArray(events)],
export class MailerLiteTrigger extends VersionedNodeType {
constructor() {
const baseDescription: INodeTypeBaseDescription = {
displayName: 'MailerLite Trigger',
name: 'mailerLiteTrigger',
icon: 'file:MailerLite.svg',
group: ['trigger'],
description: 'Starts the workflow when MailerLite events occur',
defaultVersion: 2,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new MailerLiteTriggerV1(baseDescription),
2: new MailerLiteTriggerV2(baseDescription),
};
super(nodeVersions, baseDescription);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 B

View file

@ -0,0 +1,253 @@
/* eslint-disable n8n-nodes-base/node-param-display-name-miscased */
import {
type IExecuteFunctions,
type ILoadOptionsFunctions,
type IHookFunctions,
NodeApiError,
} from 'n8n-workflow';
import {
getCustomFields,
mailerliteApiRequest,
mailerliteApiRequestAllItems,
} from '../GenericFunctions';
describe('MailerLite -> mailerliteApiRequest', () => {
let mockExecuteFunctions: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions;
const setupMockFunctions = (typeVersion: number) => {
mockExecuteFunctions = {
getNode: jest.fn().mockReturnValue({ typeVersion }),
helpers: {
httpRequestWithAuthentication: jest.fn(),
},
} as unknown as IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions;
jest.clearAllMocks();
};
beforeEach(() => {
setupMockFunctions(1);
});
it('should make a successful API request for type version 1', async () => {
const method = 'GET';
const path = '/test';
const body = {};
const qs = {};
const responseData = { success: true };
(mockExecuteFunctions.helpers.httpRequestWithAuthentication as jest.Mock).mockResolvedValue(
responseData,
);
const result = await mailerliteApiRequest.call(mockExecuteFunctions, method, path, body, qs);
expect(result).toEqual(responseData);
expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'mailerLiteApi',
{
method,
qs,
url: 'https://api.mailerlite.com/api/v2/test',
json: true,
},
);
});
it('should make a successful API request for type version 2', async () => {
setupMockFunctions(2);
const method = 'GET';
const path = '/test';
const body = {};
const qs = {};
const responseData = { success: true };
(mockExecuteFunctions.helpers.httpRequestWithAuthentication as jest.Mock).mockResolvedValue(
responseData,
);
const result = await mailerliteApiRequest.call(mockExecuteFunctions, method, path, body, qs);
expect(result).toEqual(responseData);
expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'mailerLiteApi',
{
method,
qs,
url: 'https://connect.mailerlite.com/api/test',
json: true,
},
);
});
it('should make an API request with an empty body', async () => {
const method = 'GET';
const path = '/test';
const body = {};
const qs = {};
const responseData = { success: true };
(mockExecuteFunctions.helpers.httpRequestWithAuthentication as jest.Mock).mockResolvedValue(
responseData,
);
const result = await mailerliteApiRequest.call(mockExecuteFunctions, method, path, body, qs);
expect(result).toEqual(responseData);
expect(mockExecuteFunctions.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'mailerLiteApi',
{
method,
qs,
url: 'https://api.mailerlite.com/api/v2/test',
json: true,
},
);
});
it('should throw an error if the API request fails', async () => {
const method = 'GET';
const path = '/test';
const body = {};
const qs = {};
const errorResponse = { message: 'Error' };
(mockExecuteFunctions.helpers.httpRequestWithAuthentication as jest.Mock).mockRejectedValue(
errorResponse,
);
await expect(
mailerliteApiRequest.call(mockExecuteFunctions, method, path, body, qs),
).rejects.toThrow(NodeApiError);
});
});
describe('MailerLite -> mailerliteApiRequestAllItems', () => {
let mockExecuteFunctions: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions;
const setupMockFunctions = (typeVersion: number) => {
mockExecuteFunctions = {
getNode: jest.fn().mockReturnValue({ typeVersion }),
helpers: {
httpRequestWithAuthentication: jest.fn(),
},
} as unknown as IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions;
jest.clearAllMocks();
};
beforeEach(() => {
setupMockFunctions(1);
});
it('should handle pagination for type version 1', async () => {
const method = 'GET';
const endpoint = '/test';
const body = {};
const query = {};
const responseDataPage1 = [{ id: 1 }, { id: 2 }];
const responseDataPage2 = [{ id: 3 }, { id: 4 }];
(mockExecuteFunctions.helpers.httpRequestWithAuthentication as jest.Mock)
.mockResolvedValueOnce(responseDataPage1)
.mockResolvedValueOnce(responseDataPage2)
.mockResolvedValueOnce([]);
const result = await mailerliteApiRequestAllItems.call(
mockExecuteFunctions,
method,
endpoint,
body,
query,
);
expect(result).toEqual([...responseDataPage1, ...responseDataPage2]);
});
it('should handle pagination for type version 2', async () => {
setupMockFunctions(2);
const method = 'GET';
const endpoint = '/test';
const body = {};
const query = {};
const responseDataPage1 = {
data: [{ id: 1 }, { id: 2 }],
meta: { next_cursor: 'cursor1' },
links: { next: 'nextLink1' },
};
const responseDataPage2 = {
data: [{ id: 3 }, { id: 4 }],
meta: { next_cursor: null },
links: { next: null },
};
(mockExecuteFunctions.helpers.httpRequestWithAuthentication as jest.Mock)
.mockResolvedValueOnce(responseDataPage1)
.mockResolvedValueOnce(responseDataPage2);
const result = await mailerliteApiRequestAllItems.call(
mockExecuteFunctions,
method,
endpoint,
body,
query,
);
expect(result).toEqual([...responseDataPage1.data, ...responseDataPage2.data]);
});
});
describe('MailerLite -> getCustomFields', () => {
let mockExecuteFunctions: ILoadOptionsFunctions;
const v1FieldResponse = [
{ name: 'Field1', key: 'field1' },
{ name: 'Field2', key: 'field2' },
];
const v2FieldResponse = {
data: v1FieldResponse,
};
const setupMockFunctions = (typeVersion: number) => {
mockExecuteFunctions = {
getNode: jest.fn().mockReturnValue({ typeVersion }),
helpers: {
httpRequestWithAuthentication: jest
.fn()
.mockResolvedValue(typeVersion === 1 ? v1FieldResponse : v2FieldResponse),
},
} as unknown as ILoadOptionsFunctions;
jest.clearAllMocks();
};
beforeEach(() => {
setupMockFunctions(1);
});
it('should return custom fields for type version 1', async () => {
const result = await getCustomFields.call(mockExecuteFunctions);
expect(result).toEqual([
{ name: 'field1', value: 'field1' },
{ name: 'field2', value: 'field2' },
]);
});
it('should return custom fields for type version 2', async () => {
setupMockFunctions(2);
const result = await getCustomFields.call(mockExecuteFunctions);
expect(result).toEqual([
{ name: 'Field1', value: 'field1' },
{ name: 'Field2', value: 'field2' },
]);
});
});

View file

@ -0,0 +1,411 @@
export const getUpdateSubscriberResponseClassic = {
id: 1343965485,
email: 'demo@mailerlite.com',
sent: 0,
opened: 0,
clicked: 0,
type: 'unsubscribed',
fields: [
{
key: 'email',
value: 'demo@mailerlite.com',
type: 'TEXT',
},
{
key: 'name',
value: 'Demo',
type: 'TEXT',
},
{
key: 'last_name',
value: '',
type: 'TEXT',
},
{
key: 'company',
value: '',
type: 'TEXT',
},
{
key: 'country',
value: '',
type: 'TEXT',
},
{
key: 'city',
value: '',
type: 'TEXT',
},
{
key: 'phone',
value: '',
type: 'TEXT',
},
{
key: 'state',
value: '',
type: 'TEXT',
},
{
key: 'zip',
value: '',
type: 'TEXT',
},
],
date_subscribe: null,
date_unsubscribe: '2016-04-04 12:07:26',
date_created: '2016-04-04',
date_updated: null,
};
export const getSubscriberResponseClassic = {
id: 1343965485,
name: 'John',
email: 'demo@mailerlite.com',
sent: 0,
opened: 0,
clicked: 0,
type: 'active',
signup_ip: '127.0.0.1',
signup_timestamp: '2018-01-01 01:01:01',
confirmation_ip: '127.0.0.2',
confirmation_timestamp: '2018-01-01 01:01:02',
fields: [
{
key: 'email',
value: 'demo@mailerlite.com',
type: 'TEXT',
},
{
key: 'name',
value: 'John',
type: 'TEXT',
},
{
key: 'last_name',
value: '',
type: 'TEXT',
},
{
key: 'company',
value: '',
type: 'TEXT',
},
{
key: 'country',
value: '',
type: 'TEXT',
},
{
key: 'city',
value: '',
type: 'TEXT',
},
{
key: 'phone',
value: '',
type: 'TEXT',
},
{
key: 'state',
value: '',
type: 'TEXT',
},
{
key: 'zip',
value: '',
type: 'TEXT',
},
],
date_subscribe: null,
date_unsubscribe: null,
date_created: '2016-04-04',
date_updated: null,
};
export const getCreateResponseClassic = {
id: 1343965485,
name: 'John',
email: 'demo@mailerlite.com',
sent: 0,
opened: 0,
clicked: 0,
type: 'active',
fields: [
{
key: 'email',
value: 'demo@mailerlite.com',
type: 'TEXT',
},
{
key: 'name',
value: 'John',
type: 'TEXT',
},
{
key: 'last_name',
value: '',
type: 'TEXT',
},
{
key: 'company',
value: 'MailerLite',
type: 'TEXT',
},
{
key: 'country',
value: '',
type: 'TEXT',
},
{
key: 'city',
value: '',
type: 'TEXT',
},
{
key: 'phone',
value: '',
type: 'TEXT',
},
{
key: 'state',
value: '',
type: 'TEXT',
},
{
key: 'zip',
value: '',
type: 'TEXT',
},
],
date_subscribe: null,
date_unsubscribe: null,
date_created: '2016-04-04 12:00:00',
date_updated: '2016-04-04 12:00:00',
};
export const getAllSubscribersResponseClassic = [
{
id: 1343965485,
name: 'John',
email: 'demo@mailerlite.com',
sent: 0,
opened: 0,
clicked: 0,
type: 'active',
fields: [
{
key: 'email',
value: 'demo@mailerlite.com',
type: 'TEXT',
},
{
key: 'name',
value: 'John',
type: 'TEXT',
},
{
key: 'last_name',
value: '',
type: 'TEXT',
},
{
key: 'company',
value: '',
type: 'TEXT',
},
{
key: 'country',
value: '',
type: 'TEXT',
},
{
key: 'city',
value: '',
type: 'TEXT',
},
{
key: 'phone',
value: '',
type: 'TEXT',
},
{
key: 'state',
value: '',
type: 'TEXT',
},
{
key: 'zip',
value: '',
type: 'TEXT',
},
],
date_subscribe: null,
date_unsubscribe: null,
date_created: '2016-04-04',
date_updated: null,
},
];
export const getUpdateSubscriberResponseV2 = {
data: {
id: '139872142007207563',
email: 'user@n8n.io',
status: 'junk',
source: 'api',
sent: 0,
opens_count: 0,
clicks_count: 0,
open_rate: 0,
click_rate: 0,
ip_address: null,
subscribed_at: '2024-12-05 09:54:29',
unsubscribed_at: null,
created_at: '2024-12-05 09:54:29',
updated_at: '2024-12-05 10:20:32',
fields: {
name: null,
last_name: null,
company: null,
country: null,
city: null,
phone: null,
state: null,
z_i_p: null,
},
groups: [],
opted_in_at: null,
optin_ip: '8.8.8.8',
},
};
export const getCreateResponseV2 = {
data: {
id: '139872142007207563',
email: 'user@n8n.io',
status: 'junk',
source: 'api',
sent: 0,
opens_count: 0,
clicks_count: 0,
open_rate: 0,
click_rate: 0,
ip_address: null,
subscribed_at: '2024-12-05 09:54:29',
unsubscribed_at: null,
created_at: '2024-12-05 09:54:29',
updated_at: '2024-12-05 10:20:32',
fields: {
name: null,
last_name: null,
company: null,
country: null,
city: null,
phone: null,
state: null,
z_i_p: null,
},
groups: [],
opted_in_at: null,
optin_ip: '8.8.8.8',
},
};
export const getSubscriberResponseV2 = {
data: {
id: '139872142007207563',
email: 'user@n8n.io',
status: 'junk',
source: 'api',
sent: 0,
opens_count: 0,
clicks_count: 0,
open_rate: 0,
click_rate: 0,
ip_address: null,
subscribed_at: '2024-12-05 09:54:29',
unsubscribed_at: null,
created_at: '2024-12-05 09:54:29',
updated_at: '2024-12-05 10:20:32',
fields: {
name: null,
last_name: null,
company: null,
country: null,
city: null,
phone: null,
state: null,
z_i_p: null,
},
groups: [],
opted_in_at: null,
optin_ip: '8.8.8.8',
},
};
export const getAllSubscribersResponseV2 = {
data: [
{
id: '139872142007207563',
email: 'user@n8n.io',
status: 'junk',
source: 'api',
sent: 0,
opens_count: 0,
clicks_count: 0,
open_rate: 0,
click_rate: 0,
ip_address: null,
subscribed_at: '2024-12-05 09:54:29',
unsubscribed_at: null,
created_at: '2024-12-05 09:54:29',
updated_at: '2024-12-05 10:20:32',
fields: {
name: null,
last_name: null,
company: null,
country: null,
city: null,
phone: null,
state: null,
z_i_p: null,
},
opted_in_at: null,
optin_ip: '8.8.8.8',
},
{
id: '139059851540038710',
email: 'nathan@n8n.io',
status: 'junk',
source: 'api',
sent: 0,
opens_count: 0,
clicks_count: 0,
open_rate: 0,
click_rate: 0,
ip_address: null,
subscribed_at: null,
unsubscribed_at: null,
created_at: '2024-11-26 10:43:28',
updated_at: '2024-11-27 10:09:34',
fields: {
name: 'Nathan',
last_name: 'Workflow',
company: null,
country: null,
city: null,
phone: null,
state: null,
z_i_p: null,
},
opted_in_at: null,
optin_ip: null,
},
],
links: {
first: null,
last: null,
prev: null,
next: null,
},
meta: {
path: 'https://connect.mailerlite.com/api/subscribers',
per_page: 2,
next_cursor: null,
prev_cursor: null,
},
};

View file

@ -0,0 +1,460 @@
{
"name": "[TEST] MailerLite v1 Node",
"nodes": [
{
"parameters": {},
"id": "be5a39ea-04bf-49a3-969f-47d4a9496f08",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-340, 280]
},
{
"parameters": {
"email": "demo@mailerlite.com",
"additionalFields": {}
},
"id": "98d30bbe-cbdd-4313-933e-804cdf322860",
"name": "Create Subscriber",
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 1,
"position": [-140, 40],
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [80, 40],
"id": "93aa764f-5101-4961-9b51-9fa92f746337",
"name": "No Operation, do nothing"
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [80, 220],
"id": "4dccb059-c4f6-4eae-b68a-5c5c36d0b8d4",
"name": "No Operation, do nothing1"
},
{
"parameters": {
"operation": "get",
"subscriberId": "demo@mailerlite.com"
},
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 1,
"position": [-140, 220],
"id": "82115adf-edf4-4ce4-9109-3ade129294d1",
"name": "Get Subscriber",
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {
"operation": "update",
"subscriberId": "demo@mailerlite.com",
"updateFields": {
"type": "active"
}
},
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 1,
"position": [-140, 420],
"id": "fae9c6bd-1bd1-4ee8-865d-283b7edb6004",
"name": "Update Subscriber",
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [80, 420],
"id": "45937d69-3956-434d-b955-a67d77d43d57",
"name": "No Operation, do nothing2"
},
{
"parameters": {
"operation": "getAll",
"limit": 1,
"filters": {}
},
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 1,
"position": [-180, 680],
"id": "6491d933-0929-44bd-89cf-977823dde650",
"name": "Get Many Subscrbers",
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [40, 680],
"id": "6e35d6e1-1ce3-4410-8558-a5d573676d8a",
"name": "No Operation, do nothing3"
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": 1343965485,
"name": "John",
"email": "demo@mailerlite.com",
"sent": 0,
"opened": 0,
"clicked": 0,
"type": "active",
"fields": [
{
"key": "email",
"value": "demo@mailerlite.com",
"type": "TEXT"
},
{
"key": "name",
"value": "John",
"type": "TEXT"
},
{
"key": "last_name",
"value": "",
"type": "TEXT"
},
{
"key": "company",
"value": "MailerLite",
"type": "TEXT"
},
{
"key": "country",
"value": "",
"type": "TEXT"
},
{
"key": "city",
"value": "",
"type": "TEXT"
},
{
"key": "phone",
"value": "",
"type": "TEXT"
},
{
"key": "state",
"value": "",
"type": "TEXT"
},
{
"key": "zip",
"value": "",
"type": "TEXT"
}
],
"date_subscribe": null,
"date_unsubscribe": null,
"date_created": "2016-04-04 12:00:00",
"date_updated": "2016-04-04 12:00:00"
}
}
],
"No Operation, do nothing1": [
{
"json": {
"id": 1343965485,
"name": "John",
"email": "demo@mailerlite.com",
"sent": 0,
"opened": 0,
"clicked": 0,
"type": "active",
"signup_ip": "127.0.0.1",
"signup_timestamp": "2018-01-01 01:01:01",
"confirmation_ip": "127.0.0.2",
"confirmation_timestamp": "2018-01-01 01:01:02",
"fields": [
{
"key": "email",
"value": "demo@mailerlite.com",
"type": "TEXT"
},
{
"key": "name",
"value": "John",
"type": "TEXT"
},
{
"key": "last_name",
"value": "",
"type": "TEXT"
},
{
"key": "company",
"value": "",
"type": "TEXT"
},
{
"key": "country",
"value": "",
"type": "TEXT"
},
{
"key": "city",
"value": "",
"type": "TEXT"
},
{
"key": "phone",
"value": "",
"type": "TEXT"
},
{
"key": "state",
"value": "",
"type": "TEXT"
},
{
"key": "zip",
"value": "",
"type": "TEXT"
}
],
"date_subscribe": null,
"date_unsubscribe": null,
"date_created": "2016-04-04",
"date_updated": null
}
}
],
"No Operation, do nothing2": [
{
"json": {
"id": 1343965485,
"email": "demo@mailerlite.com",
"sent": 0,
"opened": 0,
"clicked": 0,
"type": "unsubscribed",
"fields": [
{
"key": "email",
"value": "demo@mailerlite.com",
"type": "TEXT"
},
{
"key": "name",
"value": "Demo",
"type": "TEXT"
},
{
"key": "last_name",
"value": "",
"type": "TEXT"
},
{
"key": "company",
"value": "",
"type": "TEXT"
},
{
"key": "country",
"value": "",
"type": "TEXT"
},
{
"key": "city",
"value": "",
"type": "TEXT"
},
{
"key": "phone",
"value": "",
"type": "TEXT"
},
{
"key": "state",
"value": "",
"type": "TEXT"
},
{
"key": "zip",
"value": "",
"type": "TEXT"
}
],
"date_subscribe": null,
"date_unsubscribe": "2016-04-04 12:07:26",
"date_created": "2016-04-04",
"date_updated": null
}
}
],
"No Operation, do nothing3": [
{
"json": {
"id": 1343965485,
"name": "John",
"email": "demo@mailerlite.com",
"sent": 0,
"opened": 0,
"clicked": 0,
"type": "active",
"fields": [
{
"key": "email",
"value": "demo@mailerlite.com",
"type": "TEXT"
},
{
"key": "name",
"value": "John",
"type": "TEXT"
},
{
"key": "last_name",
"value": "",
"type": "TEXT"
},
{
"key": "company",
"value": "",
"type": "TEXT"
},
{
"key": "country",
"value": "",
"type": "TEXT"
},
{
"key": "city",
"value": "",
"type": "TEXT"
},
{
"key": "phone",
"value": "",
"type": "TEXT"
},
{
"key": "state",
"value": "",
"type": "TEXT"
},
{
"key": "zip",
"value": "",
"type": "TEXT"
}
],
"date_subscribe": null,
"date_unsubscribe": null,
"date_created": "2016-04-04",
"date_updated": null
}
}
]
},
"connections": {
"When clicking Test workflow": {
"main": [
[
{
"node": "Create Subscriber",
"type": "main",
"index": 0
},
{
"node": "Get Subscriber",
"type": "main",
"index": 0
},
{
"node": "Update Subscriber",
"type": "main",
"index": 0
},
{
"node": "Get Many Subscrbers",
"type": "main",
"index": 0
}
]
]
},
"Create Subscriber": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"Get Subscriber": {
"main": [
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
},
"Update Subscriber": {
"main": [
[
{
"node": "No Operation, do nothing2",
"type": "main",
"index": 0
}
]
]
},
"Get Many Subscrbers": {
"main": [
[
{
"node": "No Operation, do nothing3",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "826c1711-fcea-4564-809b-0258dbdd72f4",
"meta": {
"instanceId": "8c8c5237b8e37b006a7adce87f4369350c58e41f3ca9de16196d3197f69eabcd"
},
"id": "I0absgO5t7xV2f2V",
"tags": []
}

View file

@ -0,0 +1,31 @@
import nock from 'nock';
import { getWorkflowFilenames, testWorkflows } from '../../../../test/nodes/Helpers';
import {
getCreateResponseClassic,
getSubscriberResponseClassic,
getAllSubscribersResponseClassic,
getUpdateSubscriberResponseClassic,
} from '../apiResponses';
describe('MailerLite', () => {
describe('Run v1 workflow', () => {
beforeAll(() => {
nock.disableNetConnect();
const mock = nock('https://api.mailerlite.com/api/v2');
mock.post('/subscribers').reply(200, getCreateResponseClassic);
mock.get('/subscribers/demo@mailerlite.com').reply(200, getSubscriberResponseClassic);
mock.get('/subscribers').query({ limit: 1 }).reply(200, getAllSubscribersResponseClassic);
mock.put('/subscribers/demo@mailerlite.com').reply(200, getUpdateSubscriberResponseClassic);
});
afterAll(() => {
nock.restore();
});
const workflows = getWorkflowFilenames(__dirname);
testWorkflows(workflows);
});
});

View file

@ -0,0 +1,370 @@
{
"name": "[TEST] MailerLite v2 Node",
"nodes": [
{
"parameters": {},
"id": "3c72284b-2b88-4d5f-81bc-b1970b14f2af",
"name": "When clicking Test workflow",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [800, 240]
},
{
"parameters": {
"email": "user@n8n.io",
"additionalFields": {
"status": "active"
}
},
"id": "702c6598-cbe8-403e-962e-56621ec727a4",
"name": "Create Subscriber",
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 2,
"position": [1000, 0],
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1220, 0],
"id": "540b98b5-b3bf-49a1-a406-acc6872f4b50",
"name": "No Operation, do nothing"
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1220, 180],
"id": "17c0b8e7-a9d7-4a4f-882f-c3fb3f6bc289",
"name": "No Operation, do nothing1"
},
{
"parameters": {
"operation": "get",
"subscriberId": "user@n8n.io"
},
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 2,
"position": [1000, 180],
"id": "5598f2b9-4d67-4ad7-a8e4-7b7bf723cd5a",
"name": "Get Subscriber",
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {
"operation": "update",
"subscriberId": "user@n8n.io",
"additionalFields": {
"status": "junk",
"optin_ip": "8.8.8.8"
}
},
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 2,
"position": [1000, 380],
"id": "223e4507-c88e-4066-a122-ccaf9cea7b49",
"name": "Update Subscriber",
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1220, 380],
"id": "94d04b52-8809-4670-a8ca-135921139fc9",
"name": "No Operation, do nothing2"
},
{
"parameters": {
"operation": "getAll",
"limit": 2,
"filters": {
"status": "junk"
}
},
"type": "n8n-nodes-base.mailerLite",
"typeVersion": 2,
"position": [960, 640],
"id": "30c6e797-ceda-4c84-8f34-b61200ffd9e9",
"name": "Get Many Subscrbers",
"credentials": {
"mailerLiteApi": {
"id": "bm7VHS2C7lRgVOhb",
"name": "Mailer Lite account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [1180, 640],
"id": "c8529a30-889b-4ac9-a509-73f5dd8eef4a",
"name": "No Operation, do nothing3"
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"id": "139872142007207563",
"email": "user@n8n.io",
"status": "junk",
"source": "api",
"sent": 0,
"opens_count": 0,
"clicks_count": 0,
"open_rate": 0,
"click_rate": 0,
"ip_address": null,
"subscribed_at": "2024-12-05 09:54:29",
"unsubscribed_at": null,
"created_at": "2024-12-05 09:54:29",
"updated_at": "2024-12-05 10:20:32",
"fields": {
"name": null,
"last_name": null,
"company": null,
"country": null,
"city": null,
"phone": null,
"state": null,
"z_i_p": null
},
"groups": [],
"opted_in_at": null,
"optin_ip": "8.8.8.8"
}
}
],
"No Operation, do nothing1": [
{
"json": {
"id": "139872142007207563",
"email": "user@n8n.io",
"status": "junk",
"source": "api",
"sent": 0,
"opens_count": 0,
"clicks_count": 0,
"open_rate": 0,
"click_rate": 0,
"ip_address": null,
"subscribed_at": "2024-12-05 09:54:29",
"unsubscribed_at": null,
"created_at": "2024-12-05 09:54:29",
"updated_at": "2024-12-05 10:20:32",
"fields": {
"name": null,
"last_name": null,
"company": null,
"country": null,
"city": null,
"phone": null,
"state": null,
"z_i_p": null
},
"groups": [],
"opted_in_at": null,
"optin_ip": "8.8.8.8"
}
}
],
"No Operation, do nothing2": [
{
"json": {
"data": {
"id": "139872142007207563",
"email": "user@n8n.io",
"status": "junk",
"source": "api",
"sent": 0,
"opens_count": 0,
"clicks_count": 0,
"open_rate": 0,
"click_rate": 0,
"ip_address": null,
"subscribed_at": "2024-12-05 09:54:29",
"unsubscribed_at": null,
"created_at": "2024-12-05 09:54:29",
"updated_at": "2024-12-05 10:20:32",
"fields": {
"name": null,
"last_name": null,
"company": null,
"country": null,
"city": null,
"phone": null,
"state": null,
"z_i_p": null
},
"groups": [],
"opted_in_at": null,
"optin_ip": "8.8.8.8"
}
}
}
],
"No Operation, do nothing3": [
{
"json": {
"id": "139872142007207563",
"email": "user@n8n.io",
"status": "junk",
"source": "api",
"sent": 0,
"opens_count": 0,
"clicks_count": 0,
"open_rate": 0,
"click_rate": 0,
"ip_address": null,
"subscribed_at": "2024-12-05 09:54:29",
"unsubscribed_at": null,
"created_at": "2024-12-05 09:54:29",
"updated_at": "2024-12-05 10:20:32",
"fields": {
"name": null,
"last_name": null,
"company": null,
"country": null,
"city": null,
"phone": null,
"state": null,
"z_i_p": null
},
"opted_in_at": null,
"optin_ip": "8.8.8.8"
}
},
{
"json": {
"id": "139059851540038710",
"email": "nathan@n8n.io",
"status": "junk",
"source": "api",
"sent": 0,
"opens_count": 0,
"clicks_count": 0,
"open_rate": 0,
"click_rate": 0,
"ip_address": null,
"subscribed_at": null,
"unsubscribed_at": null,
"created_at": "2024-11-26 10:43:28",
"updated_at": "2024-11-27 10:09:34",
"fields": {
"name": "Nathan",
"last_name": "Workflow",
"company": null,
"country": null,
"city": null,
"phone": null,
"state": null,
"z_i_p": null
},
"opted_in_at": null,
"optin_ip": null
}
}
]
},
"connections": {
"When clicking Test workflow": {
"main": [
[
{
"node": "Create Subscriber",
"type": "main",
"index": 0
},
{
"node": "Get Subscriber",
"type": "main",
"index": 0
},
{
"node": "Update Subscriber",
"type": "main",
"index": 0
},
{
"node": "Get Many Subscrbers",
"type": "main",
"index": 0
}
]
]
},
"Create Subscriber": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"Get Subscriber": {
"main": [
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
},
"Update Subscriber": {
"main": [
[
{
"node": "No Operation, do nothing2",
"type": "main",
"index": 0
}
]
]
},
"Get Many Subscrbers": {
"main": [
[
{
"node": "No Operation, do nothing3",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "338331ef-1b38-47dc-9e2b-45340ea3fe3b",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "8c8c5237b8e37b006a7adce87f4369350c58e41f3ca9de16196d3197f69eabcd"
},
"id": "0Ov6Vd62DUXrWWQH",
"tags": []
}

View file

@ -0,0 +1,34 @@
import nock from 'nock';
import { getWorkflowFilenames, testWorkflows } from '../../../../test/nodes/Helpers';
import {
getCreateResponseV2,
getSubscriberResponseV2,
getAllSubscribersResponseV2,
getUpdateSubscriberResponseV2,
} from '../apiResponses';
describe('MailerLite', () => {
describe('Run v2 workflow', () => {
beforeAll(() => {
nock.disableNetConnect();
const mock = nock('https://connect.mailerlite.com/api');
mock.post('/subscribers').reply(200, getCreateResponseV2);
mock.get('/subscribers/user@n8n.io').reply(200, getSubscriberResponseV2);
mock
.get('/subscribers')
.query({ 'filter[status]': 'junk', limit: 2 })
.reply(200, getAllSubscribersResponseV2);
mock.put('/subscribers/user@n8n.io').reply(200, getUpdateSubscriberResponseV2);
});
afterAll(() => {
nock.restore();
});
const workflows = getWorkflowFilenames(__dirname);
testWorkflows(workflows);
});
});

View file

@ -0,0 +1,184 @@
import {
type IHookFunctions,
type IWebhookFunctions,
type IDataObject,
type INodeType,
type INodeTypeDescription,
type IWebhookResponseData,
type INodeTypeBaseDescription,
NodeConnectionType,
} from 'n8n-workflow';
import { mailerliteApiRequest } from '../GenericFunctions';
export class MailerLiteTriggerV1 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
displayName: 'MailerLite Trigger',
name: 'mailerLiteTrigger',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when MailerLite events occur',
defaults: {
name: 'MailerLite Trigger',
},
inputs: [],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'mailerLiteApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Event',
name: 'event',
type: 'options',
options: [
{
name: 'Campaign Sent',
value: 'campaign.sent',
description: 'Fired when campaign is sent',
},
{
name: 'Subscriber Added Through Webform',
value: 'subscriber.added_through_webform',
description: 'Fired when a subscriber is added though a form',
},
{
name: 'Subscriber Added to Group',
value: 'subscriber.add_to_group',
description: 'Fired when a subscriber is added to a group',
},
{
name: 'Subscriber Automation Completed',
value: 'subscriber.automation_complete',
description: 'Fired when subscriber finishes automation',
},
{
name: 'Subscriber Automation Triggered',
value: 'subscriber.automation_triggered',
description: 'Fired when subscriber starts automation',
},
{
name: 'Subscriber Bounced',
value: 'subscriber.bounced',
description: 'Fired when an email address bounces',
},
{
name: 'Subscriber Complained',
value: 'subscriber.complaint',
description: 'Fired when subscriber marks a campaign as a spam',
},
{
name: 'Subscriber Created',
value: 'subscriber.create',
description: 'Fired when a new subscriber is added to an account',
},
{
name: 'Subscriber Removed From Group',
value: 'subscriber.remove_from_group',
description: 'Fired when a subscriber is removed from a group',
},
{
name: 'Subscriber Unsubscribe',
value: 'subscriber.unsubscribe',
description: 'Fired when a subscriber becomes unsubscribed',
},
{
name: 'Subscriber Updated',
value: 'subscriber.update',
description: "Fired when any of the subscriber's custom fields are updated",
},
],
required: true,
default: [],
description: 'The events to listen to',
},
],
};
}
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const webhookData = this.getWorkflowStaticData('node');
const event = this.getNodeParameter('event') as string;
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const endpoint = '/webhooks';
const { webhooks } = await mailerliteApiRequest.call(this, 'GET', endpoint, {});
for (const webhook of webhooks) {
if (webhook.url === webhookUrl && webhook.event === event) {
// Set webhook-id to be sure that it can be deleted
webhookData.webhookId = webhook.id as string;
return true;
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const event = this.getNodeParameter('event') as string;
const endpoint = '/webhooks';
const body = {
url: webhookUrl,
event,
};
const responseData = await mailerliteApiRequest.call(this, 'POST', endpoint, body);
if (responseData.id === undefined) {
// Required data is missing so was not successful
return false;
}
webhookData.webhookId = responseData.id as string;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const endpoint = `/webhooks/${webhookData.webhookId}`;
try {
await mailerliteApiRequest.call(this, 'DELETE', endpoint);
} catch (error) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const body = this.getBodyData();
const events = body.events as IDataObject[];
return {
workflowData: [this.helpers.returnJsonArray(events)],
};
}
}

View file

@ -0,0 +1,199 @@
import type {
IExecuteFunctions,
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
INodeTypeBaseDescription,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { subscriberFields, subscriberOperations } from './SubscriberDescription';
import {
getCustomFields,
mailerliteApiRequest,
mailerliteApiRequestAllItems,
} from '../GenericFunctions';
export class MailerLiteV1 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
displayName: 'MailerLite',
name: 'mailerLite',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Mailer Lite API',
defaults: {
name: 'MailerLite',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'mailerLiteApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Subscriber',
value: 'subscriber',
},
],
default: 'subscriber',
},
...subscriberOperations,
...subscriberFields,
],
};
}
methods = {
loadOptions: {
getCustomFields,
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const length = items.length;
const qs: IDataObject = {};
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
try {
if (resource === 'subscriber') {
//https://developers.mailerlite.com/reference#create-a-subscriber
if (operation === 'create') {
const email = this.getNodeParameter('email', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IDataObject = {
email,
fields: [],
};
Object.assign(body, additionalFields);
if (additionalFields.customFieldsUi) {
const customFieldsValues = (additionalFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[];
if (customFieldsValues) {
const fields = {};
for (const customFieldValue of customFieldsValues) {
//@ts-ignore
fields[customFieldValue.fieldId] = customFieldValue.value;
}
body.fields = fields;
delete body.customFieldsUi;
}
}
responseData = await mailerliteApiRequest.call(this, 'POST', '/subscribers', body);
}
//https://developers.mailerlite.com/reference#single-subscriber
if (operation === 'get') {
const subscriberId = this.getNodeParameter('subscriberId', i) as string;
responseData = await mailerliteApiRequest.call(
this,
'GET',
`/subscribers/${subscriberId}`,
);
}
//https://developers.mailerlite.com/reference#subscribers
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
const filters = this.getNodeParameter('filters', i);
Object.assign(qs, filters);
if (returnAll) {
responseData = await mailerliteApiRequestAllItems.call(
this,
'GET',
'/subscribers',
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await mailerliteApiRequest.call(this, 'GET', '/subscribers', {}, qs);
}
}
//https://developers.mailerlite.com/reference#update-subscriber
if (operation === 'update') {
const subscriberId = this.getNodeParameter('subscriberId', i) as string;
const updateFields = this.getNodeParameter('updateFields', i);
const body: IDataObject = {};
Object.assign(body, updateFields);
if (updateFields.customFieldsUi) {
const customFieldsValues = (updateFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[];
if (customFieldsValues) {
const fields = {};
for (const customFieldValue of customFieldsValues) {
//@ts-ignore
fields[customFieldValue.fieldId] = customFieldValue.value;
}
body.fields = fields;
delete body.customFieldsUi;
}
}
responseData = await mailerliteApiRequest.call(
this,
'PUT',
`/subscribers/${subscriberId}`,
body,
);
}
}
} 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;
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
return [returnData];
}
}

View file

@ -0,0 +1,35 @@
export interface CustomField {
name: string;
key: string;
}
export interface SubscriberFields {
city: string | null;
company: string | null;
country: string | null;
last_name: string | null;
name: string | null;
phone: string | null;
state: string | null;
z_i_p: string | null;
}
export interface Subscriber {
id: string;
email: string;
status: string;
source: string;
sent: number;
opens_count: number;
clicks_count: number;
open_rate: number;
click_rate: number;
ip_address: string | null;
subscribed_at: string;
unsubscribed_at: string | null;
created_at: string;
updated_at: string;
fields: SubscriberFields;
opted_in_at: string | null;
optin_ip: string | null;
}

View file

@ -0,0 +1,179 @@
import {
type IHookFunctions,
type IWebhookFunctions,
type IDataObject,
type INodeType,
type INodeTypeDescription,
type IWebhookResponseData,
type INodeTypeBaseDescription,
NodeConnectionType,
} from 'n8n-workflow';
import { mailerliteApiRequest } from '../GenericFunctions';
export class MailerLiteTriggerV2 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
displayName: 'MailerLite Trigger',
name: 'mailerLiteTrigger',
group: ['trigger'],
version: [2],
description: 'Starts the workflow when MailerLite events occur',
defaults: {
name: 'MailerLite Trigger',
},
inputs: [],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'mailerLiteApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'Campaign Sent',
value: 'campaign.sent',
description: 'Fired when campaign is sent',
},
{
name: 'Subscriber Added to Group',
value: 'subscriber.added_to_group',
description: 'Fired when a subscriber is added to a group',
},
{
name: 'Subscriber Automation Completed',
value: 'subscriber.automation_completed',
description: 'Fired when subscriber finishes automation',
},
{
name: 'Subscriber Automation Triggered',
value: 'subscriber.automation_triggered',
description: 'Fired when subscriber starts automation',
},
{
name: 'Subscriber Bounced',
value: 'subscriber.bounced',
description: 'Fired when an email address bounces',
},
{
name: 'Subscriber Created',
value: 'subscriber.created',
description: 'Fired when a new subscriber is added to an account',
},
{
name: 'Subscriber Removed From Group',
value: 'subscriber.removed_from_group',
description: 'Fired when a subscriber is removed from a group',
},
{
name: 'Subscriber Spam Reported',
value: 'subscriber.spam_reported',
description: 'Fired when subscriber marks a campaign as a spam',
},
{
name: 'Subscriber Unsubscribe',
value: 'subscriber.unsubscribed',
description: 'Fired when a subscriber becomes unsubscribed',
},
{
name: 'Subscriber Updated',
value: 'subscriber.updated',
description: "Fired when any of the subscriber's custom fields are updated",
},
],
required: true,
default: [],
description: 'The events to listen to',
},
],
};
}
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
const webhookData = this.getWorkflowStaticData('node');
const events = this.getNodeParameter('events') as string[];
// Check all the webhooks which exist already if it is identical to the
// one that is supposed to get created.
const endpoint = '/webhooks';
const { data } = await mailerliteApiRequest.call(this, 'GET', endpoint, {});
for (const webhook of data) {
if (webhook.url === webhookUrl && webhook.events === events) {
// Set webhook-id to be sure that it can be deleted
webhookData.webhookId = webhook.id as string;
return true;
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const events = this.getNodeParameter('events') as string[];
const endpoint = '/webhooks';
const body = {
url: webhookUrl,
events,
};
const { data } = await mailerliteApiRequest.call(this, 'POST', endpoint, body);
if (data.id === undefined) {
// Required data is missing so was not successful
return false;
}
webhookData.webhookId = data.id as string;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const endpoint = `/webhooks/${webhookData.webhookId}`;
try {
await mailerliteApiRequest.call(this, 'DELETE', endpoint);
} catch (error) {
return false;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registered anymore
delete webhookData.webhookId;
}
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const body = this.getBodyData();
const data = body.fields as IDataObject[];
return {
workflowData: [this.helpers.returnJsonArray(data)],
};
}
}

View file

@ -0,0 +1,206 @@
import type {
IExecuteFunctions,
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
INodeTypeBaseDescription,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import type { Subscriber } from './MailerLite.Interface';
import { subscriberFields, subscriberOperations } from './SubscriberDescription';
import {
getCustomFields,
mailerliteApiRequest,
mailerliteApiRequestAllItems,
} from '../GenericFunctions';
export class MailerLiteV2 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
displayName: 'MailerLite',
name: 'mailerLite',
group: ['input'],
version: [2],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Mailer Lite API',
defaults: {
name: 'MailerLite',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'mailerLiteApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Subscriber',
value: 'subscriber',
},
],
default: 'subscriber',
},
...subscriberOperations,
...subscriberFields,
],
};
}
methods = {
loadOptions: {
getCustomFields,
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const length = items.length;
const qs: IDataObject = {};
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
try {
if (resource === 'subscriber') {
//https://developers.mailerlite.com/reference#create-a-subscriber
if (operation === 'create') {
const email = this.getNodeParameter('email', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IDataObject = {
email,
fields: [],
};
Object.assign(body, additionalFields);
if (additionalFields.customFieldsUi) {
const customFieldsValues = (additionalFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[];
if (customFieldsValues) {
const fields = {};
for (const customFieldValue of customFieldsValues) {
//@ts-ignore
fields[customFieldValue.fieldId] = customFieldValue.value;
}
body.fields = fields;
delete body.customFieldsUi;
}
}
responseData = await mailerliteApiRequest.call(this, 'POST', '/subscribers', body);
responseData = responseData.data;
}
//https://developers.mailerlite.com/reference#single-subscriber
if (operation === 'get') {
const subscriberId = this.getNodeParameter('subscriberId', i) as string;
responseData = await mailerliteApiRequest.call(
this,
'GET',
`/subscribers/${subscriberId}`,
);
responseData = responseData.data as Subscriber[];
}
//https://developers.mailerlite.com/reference#subscribers
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i);
const filters = this.getNodeParameter('filters', i);
if (filters.status) {
qs['filter[status]'] = filters.status as string;
}
if (returnAll) {
responseData = await mailerliteApiRequestAllItems.call(
this,
'GET',
'/subscribers',
{},
qs,
);
} else {
qs.limit = this.getNodeParameter('limit', i);
responseData = await mailerliteApiRequest.call(this, 'GET', '/subscribers', {}, qs);
responseData = responseData.data;
}
}
//https://developers.mailerlite.com/reference#update-subscriber
if (operation === 'update') {
const subscriberId = this.getNodeParameter('subscriberId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const body: IDataObject = {};
Object.assign(body, additionalFields);
if (additionalFields.customFieldsUi) {
const customFieldsValues = (additionalFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[];
if (customFieldsValues) {
const fields = {};
for (const customFieldValue of customFieldsValues) {
//@ts-ignore
fields[customFieldValue.fieldId] = customFieldValue.value;
}
body.fields = fields;
delete body.customFieldsUi;
}
}
responseData = await mailerliteApiRequest.call(
this,
'PUT',
`/subscribers/${subscriberId}`,
body,
);
}
}
} 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;
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
return [returnData];
}
}

View file

@ -0,0 +1,304 @@
import type { INodeProperties } from 'n8n-workflow';
export const subscriberOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['subscriber'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new subscriber',
action: 'Create a subscriber',
},
{
name: 'Get',
value: 'get',
description: 'Get an subscriber',
action: 'Get a subscriber',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Get many subscribers',
action: 'Get many subscribers',
},
{
name: 'Update',
value: 'update',
description: 'Update an subscriber',
action: 'Update a subscriber',
},
],
default: 'create',
},
];
export const subscriberFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* subscriber:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Email',
name: 'email',
type: 'string',
placeholder: 'name@email.com',
required: true,
default: '',
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['create'],
},
},
description: 'Email of new subscriber',
},
/* -------------------------------------------------------------------------- */
/* subscriber:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Subscriber Email',
name: 'subscriberId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['update'],
},
},
default: '',
description: 'Email of subscriber',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['update', 'create'],
},
},
options: [
{
displayName: 'Custom Fields',
name: 'customFieldsUi',
placeholder: 'Add Custom Field',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
description: 'Filter by custom fields',
default: {},
options: [
{
name: 'customFieldsValues',
displayName: 'Custom Field',
values: [
{
displayName: 'Field Name or ID',
name: 'fieldId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
},
default: '',
description:
'The ID of the field to add custom field to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'The value to set on custom field',
},
],
},
],
},
{
displayName: 'Status',
name: 'status',
type: 'options',
options: [
{
name: 'Active',
value: 'active',
},
{
name: 'Bounced',
value: 'bounced',
},
{
name: 'Junk',
value: 'junk',
},
{
name: 'Unconfirmed',
value: 'unconfirmed',
},
{
name: 'Unsubscribed',
value: 'unsubscribed',
},
],
default: '',
},
{
displayName: 'Subscribed At',
name: 'subscribed_at',
type: 'dateTime',
default: '',
},
{
displayName: 'IP Address',
name: 'ip_address',
type: 'string',
default: '',
},
{
displayName: 'Opted In At',
name: 'opted_in_at',
type: 'dateTime',
default: '',
},
{
displayName: 'Opt In IP',
name: 'optin_ip',
type: 'string',
default: '',
},
{
displayName: 'Unsubscribed At',
name: 'unsubscribed_at',
type: 'dateTime',
default: '',
},
],
},
/* -------------------------------------------------------------------------- */
/* subscriber:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Subscriber Email',
name: 'subscriberId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['delete'],
},
},
default: '',
description: 'Email of subscriber to delete',
},
/* -------------------------------------------------------------------------- */
/* subscriber:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Subscriber Email',
name: 'subscriberId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['get'],
},
},
default: '',
description: 'Email of subscriber to get',
},
/* -------------------------------------------------------------------------- */
/* subscriber:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['subscriber'],
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['subscriber'],
},
},
default: {},
options: [
{
displayName: 'Status',
name: 'status',
type: 'options',
options: [
{
name: 'Active',
value: 'active',
},
{
name: 'Bounced',
value: 'bounced',
},
{
name: 'Junk',
value: 'junk',
},
{
name: 'Unconfirmed',
value: 'unconfirmed',
},
{
name: 'Unsubscribed',
value: 'unsubscribed',
},
],
default: '',
},
],
},
];