diff --git a/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts b/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts
index e95bebe507..be72356690 100644
--- a/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts
+++ b/packages/nodes-base/nodes/Lemlist/GenericFunctions.ts
@@ -59,13 +59,23 @@ export async function lemlistApiRequestAllItems(
qs.limit = 100;
qs.offset = 0;
-
- do {
- responseData = await lemlistApiRequest.call(this, method, endpoint, {}, qs);
- returnData.push(...(responseData as IDataObject[]));
- qs.offset += qs.limit;
- } while (responseData.length !== 0);
- return returnData;
+ //when using v2, the pagination is different
+ if (qs.version && qs.version === 'v2') {
+ qs.page = 1;
+ do {
+ responseData = await lemlistApiRequest.call(this, method, endpoint, {}, qs);
+ returnData.push(...(responseData as IDataObject[]));
+ qs.page++;
+ } while (responseData.totalPage && qs.page < responseData.totalPage);
+ return returnData;
+ } else {
+ do {
+ responseData = await lemlistApiRequest.call(this, method, endpoint, {}, qs);
+ returnData.push(...(responseData as IDataObject[]));
+ qs.offset += qs.limit;
+ } while (responseData.length !== 0);
+ return returnData;
+ }
}
export function getEvents() {
diff --git a/packages/nodes-base/nodes/Lemlist/Lemlist.node.ts b/packages/nodes-base/nodes/Lemlist/Lemlist.node.ts
index 9e55d83759..e9b557ec04 100644
--- a/packages/nodes-base/nodes/Lemlist/Lemlist.node.ts
+++ b/packages/nodes-base/nodes/Lemlist/Lemlist.node.ts
@@ -1,314 +1,25 @@
-import {
- type IExecuteFunctions,
- type IDataObject,
- type ILoadOptionsFunctions,
- type INodeExecutionData,
- type INodeType,
- type INodeTypeDescription,
- NodeConnectionType,
-} from 'n8n-workflow';
+import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
+import { VersionedNodeType } from 'n8n-workflow';
-import isEmpty from 'lodash/isEmpty';
-import omit from 'lodash/omit';
-import {
- activityFields,
- activityOperations,
- campaignFields,
- campaignOperations,
- leadFields,
- leadOperations,
- teamFields,
- teamOperations,
- unsubscribeFields,
- unsubscribeOperations,
-} from './descriptions';
+import { LemlistV1 } from './v1/LemlistV1.node';
+import { LemlistV2 } from './v2/LemlistV2.node';
-import { lemlistApiRequest, lemlistApiRequestAllItems } from './GenericFunctions';
+export class Lemlist extends VersionedNodeType {
+ constructor() {
+ const baseDescription: INodeTypeBaseDescription = {
+ displayName: 'Lemlist',
+ name: 'lemlist',
+ icon: 'file:lemlist.svg',
+ group: ['transform'],
+ defaultVersion: 2,
+ description: 'Consume the Lemlist API',
+ };
-export class Lemlist implements INodeType {
- description: INodeTypeDescription = {
- displayName: 'Lemlist',
- name: 'lemlist',
- icon: 'file:lemlist.svg',
- group: ['transform'],
- version: 1,
- subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
- description: 'Consume the Lemlist API',
- defaults: {
- name: 'Lemlist',
- },
- inputs: [NodeConnectionType.Main],
- outputs: [NodeConnectionType.Main],
- credentials: [
- {
- name: 'lemlistApi',
- required: true,
- },
- ],
- properties: [
- {
- displayName: 'Resource',
- name: 'resource',
- type: 'options',
- noDataExpression: true,
- options: [
- {
- name: 'Activity',
- value: 'activity',
- },
- {
- name: 'Campaign',
- value: 'campaign',
- },
- {
- name: 'Lead',
- value: 'lead',
- },
- {
- name: 'Team',
- value: 'team',
- },
- {
- name: 'Unsubscribe',
- value: 'unsubscribe',
- },
- ],
- default: 'activity',
- },
- ...activityOperations,
- ...activityFields,
- ...campaignOperations,
- ...campaignFields,
- ...leadOperations,
- ...leadFields,
- ...teamOperations,
- ...teamFields,
- ...unsubscribeOperations,
- ...unsubscribeFields,
- ],
- };
+ const nodeVersions: IVersionedNodeType['nodeVersions'] = {
+ 1: new LemlistV1(baseDescription),
+ 2: new LemlistV2(baseDescription),
+ };
- methods = {
- loadOptions: {
- async getCampaigns(this: ILoadOptionsFunctions) {
- const campaigns = await lemlistApiRequest.call(this, 'GET', '/campaigns');
- return campaigns.map(({ _id, name }: { _id: string; name: string }) => ({
- name,
- value: _id,
- }));
- },
- },
- };
-
- async execute(this: IExecuteFunctions) {
- const items = this.getInputData();
-
- const resource = this.getNodeParameter('resource', 0);
- const operation = this.getNodeParameter('operation', 0);
-
- let responseData;
- const returnData: INodeExecutionData[] = [];
-
- for (let i = 0; i < items.length; i++) {
- try {
- if (resource === 'activity') {
- // *********************************************************************
- // activity
- // *********************************************************************
-
- if (operation === 'getAll') {
- // ----------------------------------
- // activity: getAll
- // ----------------------------------
-
- // https://developer.lemlist.com/#activities
-
- const returnAll = this.getNodeParameter('returnAll', i);
-
- const qs = {} as IDataObject;
- const filters = this.getNodeParameter('filters', i);
-
- if (!isEmpty(filters)) {
- Object.assign(qs, filters);
- }
-
- if (returnAll) {
- responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/activities', qs);
- } else {
- qs.limit = this.getNodeParameter('limit', i);
- responseData = await lemlistApiRequest.call(this, 'GET', '/activities', {}, qs);
- }
- }
- } else if (resource === 'campaign') {
- // *********************************************************************
- // campaign
- // *********************************************************************
-
- if (operation === 'getAll') {
- // ----------------------------------
- // campaign: getAll
- // ----------------------------------
-
- // https://developer.lemlist.com/#list-all-campaigns
-
- const returnAll = this.getNodeParameter('returnAll', i);
-
- if (returnAll) {
- responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/campaigns', {});
- } else {
- const qs = {
- limit: this.getNodeParameter('limit', i),
- };
- responseData = await lemlistApiRequest.call(this, 'GET', '/campaigns', {}, qs);
- }
- }
- } else if (resource === 'lead') {
- // *********************************************************************
- // lead
- // *********************************************************************
-
- if (operation === 'create') {
- // ----------------------------------
- // lead: create
- // ----------------------------------
-
- // https://developer.lemlist.com/#add-a-lead-in-a-campaign
-
- const qs = {} as IDataObject;
- const additionalFields = this.getNodeParameter('additionalFields', i);
-
- if (additionalFields.deduplicate !== undefined) {
- qs.deduplicate = additionalFields.deduplicate;
- }
-
- const body = {} as IDataObject;
-
- const remainingAdditionalFields = omit(additionalFields, 'deduplicate');
-
- if (!isEmpty(remainingAdditionalFields)) {
- Object.assign(body, remainingAdditionalFields);
- }
-
- const campaignId = this.getNodeParameter('campaignId', i);
- const email = this.getNodeParameter('email', i);
- const endpoint = `/campaigns/${campaignId}/leads/${email}`;
-
- responseData = await lemlistApiRequest.call(this, 'POST', endpoint, body, qs);
- } else if (operation === 'delete') {
- // ----------------------------------
- // lead: delete
- // ----------------------------------
-
- // https://developer.lemlist.com/#delete-a-lead-from-a-campaign
-
- const campaignId = this.getNodeParameter('campaignId', i);
- const email = this.getNodeParameter('email', i);
- const endpoint = `/campaigns/${campaignId}/leads/${email}`;
- responseData = await lemlistApiRequest.call(
- this,
- 'DELETE',
- endpoint,
- {},
- { action: 'remove' },
- );
- } else if (operation === 'get') {
- // ----------------------------------
- // lead: get
- // ----------------------------------
-
- // https://developer.lemlist.com/#get-a-specific-lead-by-email
-
- const email = this.getNodeParameter('email', i);
- responseData = await lemlistApiRequest.call(this, 'GET', `/leads/${email}`);
- } else if (operation === 'unsubscribe') {
- // ----------------------------------
- // lead: unsubscribe
- // ----------------------------------
-
- // https://developer.lemlist.com/#unsubscribe-a-lead-from-a-campaign
-
- const campaignId = this.getNodeParameter('campaignId', i);
- const email = this.getNodeParameter('email', i);
- const endpoint = `/campaigns/${campaignId}/leads/${email}`;
- responseData = await lemlistApiRequest.call(this, 'DELETE', endpoint);
- }
- } else if (resource === 'team') {
- // *********************************************************************
- // team
- // *********************************************************************
-
- if (operation === 'get') {
- // ----------------------------------
- // team: get
- // ----------------------------------
-
- // https://developer.lemlist.com/#team
-
- responseData = await lemlistApiRequest.call(this, 'GET', '/team');
- }
- } else if (resource === 'unsubscribe') {
- // *********************************************************************
- // unsubscribe
- // *********************************************************************
-
- if (operation === 'add') {
- // ----------------------------------
- // unsubscribe: Add
- // ----------------------------------
-
- // https://developer.lemlist.com/#add-an-email-address-in-the-unsubscribes
-
- const email = this.getNodeParameter('email', i);
- responseData = await lemlistApiRequest.call(this, 'POST', `/unsubscribes/${email}`);
- } else if (operation === 'delete') {
- // ----------------------------------
- // unsubscribe: delete
- // ----------------------------------
-
- // https://developer.lemlist.com/#delete-an-email-address-from-the-unsubscribes
-
- const email = this.getNodeParameter('email', i);
- responseData = await lemlistApiRequest.call(this, 'DELETE', `/unsubscribes/${email}`);
- } else if (operation === 'getAll') {
- // ----------------------------------
- // unsubscribe: getAll
- // ----------------------------------
-
- // https://developer.lemlist.com/#list-all-unsubscribes
-
- const returnAll = this.getNodeParameter('returnAll', i);
-
- if (returnAll) {
- responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/unsubscribes', {});
- } else {
- const qs = {
- limit: this.getNodeParameter('limit', i),
- };
- responseData = await lemlistApiRequest.call(this, 'GET', '/unsubscribes', {}, qs);
- }
- }
- }
- } 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);
}
}
diff --git a/packages/nodes-base/nodes/Lemlist/test/GenericFunctions.test.ts b/packages/nodes-base/nodes/Lemlist/test/GenericFunctions.test.ts
new file mode 100644
index 0000000000..9b8e1890f5
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/test/GenericFunctions.test.ts
@@ -0,0 +1,115 @@
+import type {
+ IExecuteFunctions,
+ IHookFunctions,
+ ILoadOptionsFunctions,
+ IDataObject,
+ IHttpRequestMethods,
+} from 'n8n-workflow';
+import { lemlistApiRequest, lemlistApiRequestAllItems, getEvents } from '../GenericFunctions';
+
+describe('GenericFunctions', () => {
+ describe('lemlistApiRequest', () => {
+ const mockThis = {
+ helpers: {
+ requestWithAuthentication: jest.fn(),
+ },
+ } as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
+
+ it('should make an authenticated API request to Lemlist', async () => {
+ const method: IHttpRequestMethods = 'GET';
+ const endpoint = '/test-endpoint';
+ const body: IDataObject = { key: 'value' };
+ const qs: IDataObject = { query: 'value' };
+ const option: IDataObject = { headers: {} };
+
+ await lemlistApiRequest.call(mockThis, method, endpoint, body, qs, option);
+
+ expect(mockThis.helpers.requestWithAuthentication).toHaveBeenCalledWith('lemlistApi', {
+ headers: {},
+ method: 'GET',
+ uri: 'https://api.lemlist.com/api/test-endpoint',
+ qs: { query: 'value' },
+ body: { key: 'value' },
+ json: true,
+ });
+ });
+ });
+
+ describe('lemlistApiRequestAllItems', () => {
+ const mockThis = {
+ helpers: {
+ requestWithAuthentication: jest
+ .fn()
+ .mockResolvedValue([{ id: 'cam_A1B2C3D4E5F6G7H8I9' }, { id: 'cam_A1B2C3D4E5F6G7H8I8' }]),
+ },
+ } as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
+
+ it('should return all results', async () => {
+ const method: IHttpRequestMethods = 'GET';
+ const endpoint = '/test-endpoint';
+ const qs: IDataObject = {};
+ qs.version = 'v2';
+
+ const result = await lemlistApiRequestAllItems.call(mockThis, method, endpoint, qs);
+
+ expect(result).toEqual([{ id: 'cam_A1B2C3D4E5F6G7H8I9' }, { id: 'cam_A1B2C3D4E5F6G7H8I8' }]);
+ });
+ });
+
+ describe('getEvents', () => {
+ it('should return a list of events with capitalized names', () => {
+ const expectedEvents = [
+ { name: '*', value: '*' },
+ { name: 'Contacted', value: 'contacted' },
+ { name: 'Hooked', value: 'hooked' },
+ { name: 'Attracted', value: 'attracted' },
+ { name: 'Warmed', value: 'warmed' },
+ { name: 'Interested', value: 'interested' },
+ { name: 'Skipped', value: 'skipped' },
+ { name: 'Not Interested', value: 'notInterested' },
+ { name: 'Emails Sent', value: 'emailsSent' },
+ { name: 'Emails Opened', value: 'emailsOpened' },
+ { name: 'Emails Clicked', value: 'emailsClicked' },
+ { name: 'Emails Replied', value: 'emailsReplied' },
+ { name: 'Emails Bounced', value: 'emailsBounced' },
+ { name: 'Emails Send Failed', value: 'emailsSendFailed' },
+ { name: 'Emails Failed', value: 'emailsFailed' },
+ { name: 'Emails Unsubscribed', value: 'emailsUnsubscribed' },
+ { name: 'Emails Interested', value: 'emailsInterested' },
+ { name: 'Emails Not Interested', value: 'emailsNotInterested' },
+ { name: 'Opportunities Done', value: 'opportunitiesDone' },
+ { name: 'Aircall Created', value: 'aircallCreated' },
+ { name: 'Aircall Ended', value: 'aircallEnded' },
+ { name: 'Aircall Done', value: 'aircallDone' },
+ { name: 'Aircall Interested', value: 'aircallInterested' },
+ { name: 'Aircall Not Interested', value: 'aircallNotInterested' },
+ { name: 'Api Done', value: 'apiDone' },
+ { name: 'Api Interested', value: 'apiInterested' },
+ { name: 'Api Not Interested', value: 'apiNotInterested' },
+ { name: 'Api Failed', value: 'apiFailed' },
+ { name: 'Linkedin Visit Done', value: 'linkedinVisitDone' },
+ { name: 'Linkedin Visit Failed', value: 'linkedinVisitFailed' },
+ { name: 'Linkedin Invite Done', value: 'linkedinInviteDone' },
+ { name: 'Linkedin Invite Failed', value: 'linkedinInviteFailed' },
+ { name: 'Linkedin Invite Accepted', value: 'linkedinInviteAccepted' },
+ { name: 'Linkedin Replied', value: 'linkedinReplied' },
+ { name: 'Linkedin Sent', value: 'linkedinSent' },
+ { name: 'Linkedin Voice Note Done', value: 'linkedinVoiceNoteDone' },
+ { name: 'Linkedin Voice Note Failed', value: 'linkedinVoiceNoteFailed' },
+ { name: 'Linkedin Interested', value: 'linkedinInterested' },
+ { name: 'Linkedin Not Interested', value: 'linkedinNotInterested' },
+ { name: 'Linkedin Send Failed', value: 'linkedinSendFailed' },
+ { name: 'Manual Interested', value: 'manualInterested' },
+ { name: 'Manual Not Interested', value: 'manualNotInterested' },
+ { name: 'Paused', value: 'paused' },
+ { name: 'Resumed', value: 'resumed' },
+ { name: 'Custom Domain Errors', value: 'customDomainErrors' },
+ { name: 'Connection Issue', value: 'connectionIssue' },
+ { name: 'Send Limit Reached', value: 'sendLimitReached' },
+ { name: 'Lemwarm Paused', value: 'lemwarmPaused' },
+ ];
+ const result = getEvents();
+ expect(result).toEqual(expectedEvents);
+ });
+ });
+});
diff --git a/packages/nodes-base/nodes/Lemlist/v1/LemlistV1.node.ts b/packages/nodes-base/nodes/Lemlist/v1/LemlistV1.node.ts
new file mode 100644
index 0000000000..a3ffc7a21f
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v1/LemlistV1.node.ts
@@ -0,0 +1,322 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import {
+ type IExecuteFunctions,
+ type IDataObject,
+ type ILoadOptionsFunctions,
+ type INodeExecutionData,
+ type INodeType,
+ type INodeTypeDescription,
+ type INodeTypeBaseDescription,
+ NodeConnectionType,
+} from 'n8n-workflow';
+
+import isEmpty from 'lodash/isEmpty';
+import omit from 'lodash/omit';
+import { lemlistApiRequest, lemlistApiRequestAllItems } from '../GenericFunctions';
+import {
+ activityFields,
+ activityOperations,
+ campaignFields,
+ campaignOperations,
+ leadFields,
+ leadOperations,
+ teamFields,
+ teamOperations,
+ unsubscribeFields,
+ unsubscribeOperations,
+} from './descriptions';
+const versionDescription: INodeTypeDescription = {
+ displayName: 'Lemlist',
+ name: 'lemlist',
+ icon: 'file:lemlist.svg',
+ group: ['transform'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Consume the Lemlist API',
+ defaults: {
+ name: 'Lemlist',
+ },
+ inputs: [NodeConnectionType.Main],
+ outputs: [NodeConnectionType.Main],
+ credentials: [
+ {
+ name: 'lemlistApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Activity',
+ value: 'activity',
+ },
+ {
+ name: 'Campaign',
+ value: 'campaign',
+ },
+ {
+ name: 'Lead',
+ value: 'lead',
+ },
+ {
+ name: 'Team',
+ value: 'team',
+ },
+ {
+ name: 'Unsubscribe',
+ value: 'unsubscribe',
+ },
+ ],
+ default: 'activity',
+ },
+ ...activityOperations,
+ ...activityFields,
+ ...campaignOperations,
+ ...campaignFields,
+ ...leadOperations,
+ ...leadFields,
+ ...teamOperations,
+ ...teamFields,
+ ...unsubscribeOperations,
+ ...unsubscribeFields,
+ ],
+};
+export class LemlistV1 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ ...versionDescription,
+ };
+ }
+
+ methods = {
+ loadOptions: {
+ async getCampaigns(this: ILoadOptionsFunctions) {
+ const campaigns = await lemlistApiRequest.call(this, 'GET', '/campaigns');
+ return campaigns.map(({ _id, name }: { _id: string; name: string }) => ({
+ name,
+ value: _id,
+ }));
+ },
+ },
+ };
+
+ async execute(this: IExecuteFunctions) {
+ const items = this.getInputData();
+
+ const resource = this.getNodeParameter('resource', 0);
+ const operation = this.getNodeParameter('operation', 0);
+
+ let responseData;
+ const returnData: INodeExecutionData[] = [];
+
+ for (let i = 0; i < items.length; i++) {
+ try {
+ if (resource === 'activity') {
+ // *********************************************************************
+ // activity
+ // *********************************************************************
+
+ if (operation === 'getAll') {
+ // ----------------------------------
+ // activity: getAll
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#activities
+
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ const qs = {} as IDataObject;
+ const filters = this.getNodeParameter('filters', i);
+
+ if (!isEmpty(filters)) {
+ Object.assign(qs, filters);
+ }
+
+ if (returnAll) {
+ responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/activities', qs);
+ } else {
+ qs.limit = this.getNodeParameter('limit', i);
+ responseData = await lemlistApiRequest.call(this, 'GET', '/activities', {}, qs);
+ }
+ }
+ } else if (resource === 'campaign') {
+ // *********************************************************************
+ // campaign
+ // *********************************************************************
+
+ if (operation === 'getAll') {
+ // ----------------------------------
+ // campaign: getAll
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#list-all-campaigns
+
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ if (returnAll) {
+ responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/campaigns', {});
+ } else {
+ const qs = {
+ limit: this.getNodeParameter('limit', i),
+ };
+ responseData = await lemlistApiRequest.call(this, 'GET', '/campaigns', {}, qs);
+ }
+ }
+ } else if (resource === 'lead') {
+ // *********************************************************************
+ // lead
+ // *********************************************************************
+
+ if (operation === 'create') {
+ // ----------------------------------
+ // lead: create
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#add-a-lead-in-a-campaign
+
+ const qs = {} as IDataObject;
+ const additionalFields = this.getNodeParameter('additionalFields', i);
+
+ if (additionalFields.deduplicate !== undefined) {
+ qs.deduplicate = additionalFields.deduplicate;
+ }
+
+ const body = {} as IDataObject;
+
+ const remainingAdditionalFields = omit(additionalFields, 'deduplicate');
+
+ if (!isEmpty(remainingAdditionalFields)) {
+ Object.assign(body, remainingAdditionalFields);
+ }
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+ const email = this.getNodeParameter('email', i);
+ const endpoint = `/campaigns/${campaignId}/leads/${email}`;
+
+ responseData = await lemlistApiRequest.call(this, 'POST', endpoint, body, qs);
+ } else if (operation === 'delete') {
+ // ----------------------------------
+ // lead: delete
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#delete-a-lead-from-a-campaign
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+ const email = this.getNodeParameter('email', i);
+ const endpoint = `/campaigns/${campaignId}/leads/${email}`;
+ responseData = await lemlistApiRequest.call(
+ this,
+ 'DELETE',
+ endpoint,
+ {},
+ { action: 'remove' },
+ );
+ } else if (operation === 'get') {
+ // ----------------------------------
+ // lead: get
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#get-a-specific-lead-by-email
+
+ const email = this.getNodeParameter('email', i);
+ responseData = await lemlistApiRequest.call(this, 'GET', `/leads/${email}`);
+ } else if (operation === 'unsubscribe') {
+ // ----------------------------------
+ // lead: unsubscribe
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#unsubscribe-a-lead-from-a-campaign
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+ const email = this.getNodeParameter('email', i);
+ const endpoint = `/campaigns/${campaignId}/leads/${email}`;
+ responseData = await lemlistApiRequest.call(this, 'DELETE', endpoint);
+ }
+ } else if (resource === 'team') {
+ // *********************************************************************
+ // team
+ // *********************************************************************
+
+ if (operation === 'get') {
+ // ----------------------------------
+ // team: get
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#team
+
+ responseData = await lemlistApiRequest.call(this, 'GET', '/team');
+ }
+ } else if (resource === 'unsubscribe') {
+ // *********************************************************************
+ // unsubscribe
+ // *********************************************************************
+
+ if (operation === 'add') {
+ // ----------------------------------
+ // unsubscribe: Add
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#add-an-email-address-in-the-unsubscribes
+
+ const email = this.getNodeParameter('email', i);
+ responseData = await lemlistApiRequest.call(this, 'POST', `/unsubscribes/${email}`);
+ } else if (operation === 'delete') {
+ // ----------------------------------
+ // unsubscribe: delete
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#delete-an-email-address-from-the-unsubscribes
+
+ const email = this.getNodeParameter('email', i);
+ responseData = await lemlistApiRequest.call(this, 'DELETE', `/unsubscribes/${email}`);
+ } else if (operation === 'getAll') {
+ // ----------------------------------
+ // unsubscribe: getAll
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#list-all-unsubscribes
+
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ if (returnAll) {
+ responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/unsubscribes', {});
+ } else {
+ const qs = {
+ limit: this.getNodeParameter('limit', i),
+ };
+ responseData = await lemlistApiRequest.call(this, 'GET', '/unsubscribes', {}, qs);
+ }
+ }
+ }
+ } 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];
+ }
+}
diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/ActivityDescription.ts b/packages/nodes-base/nodes/Lemlist/v1/descriptions/ActivityDescription.ts
similarity index 100%
rename from packages/nodes-base/nodes/Lemlist/descriptions/ActivityDescription.ts
rename to packages/nodes-base/nodes/Lemlist/v1/descriptions/ActivityDescription.ts
diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/CampaignDescription.ts b/packages/nodes-base/nodes/Lemlist/v1/descriptions/CampaignDescription.ts
similarity index 100%
rename from packages/nodes-base/nodes/Lemlist/descriptions/CampaignDescription.ts
rename to packages/nodes-base/nodes/Lemlist/v1/descriptions/CampaignDescription.ts
diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/LeadDescription.ts b/packages/nodes-base/nodes/Lemlist/v1/descriptions/LeadDescription.ts
similarity index 100%
rename from packages/nodes-base/nodes/Lemlist/descriptions/LeadDescription.ts
rename to packages/nodes-base/nodes/Lemlist/v1/descriptions/LeadDescription.ts
diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/TeamDescription.ts b/packages/nodes-base/nodes/Lemlist/v1/descriptions/TeamDescription.ts
similarity index 100%
rename from packages/nodes-base/nodes/Lemlist/descriptions/TeamDescription.ts
rename to packages/nodes-base/nodes/Lemlist/v1/descriptions/TeamDescription.ts
diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/UnsubscribeDescription.ts b/packages/nodes-base/nodes/Lemlist/v1/descriptions/UnsubscribeDescription.ts
similarity index 100%
rename from packages/nodes-base/nodes/Lemlist/descriptions/UnsubscribeDescription.ts
rename to packages/nodes-base/nodes/Lemlist/v1/descriptions/UnsubscribeDescription.ts
diff --git a/packages/nodes-base/nodes/Lemlist/descriptions/index.ts b/packages/nodes-base/nodes/Lemlist/v1/descriptions/index.ts
similarity index 100%
rename from packages/nodes-base/nodes/Lemlist/descriptions/index.ts
rename to packages/nodes-base/nodes/Lemlist/v1/descriptions/index.ts
diff --git a/packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts b/packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts
new file mode 100644
index 0000000000..a88dbdbcc9
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts
@@ -0,0 +1,417 @@
+/* eslint-disable n8n-nodes-base/node-filename-against-convention */
+import {
+ type IExecuteFunctions,
+ type IDataObject,
+ type ILoadOptionsFunctions,
+ type INodeExecutionData,
+ type INodeType,
+ type INodeTypeDescription,
+ type INodeTypeBaseDescription,
+ NodeConnectionType,
+} from 'n8n-workflow';
+
+import isEmpty from 'lodash/isEmpty';
+import omit from 'lodash/omit';
+import { lemlistApiRequest, lemlistApiRequestAllItems } from '../GenericFunctions';
+import {
+ activityFields,
+ activityOperations,
+ campaignFields,
+ campaignOperations,
+ enrichmentFields,
+ enrichmentOperations,
+ leadFields,
+ leadOperations,
+ teamFields,
+ teamOperations,
+ unsubscribeFields,
+ unsubscribeOperations,
+} from './descriptions';
+const versionDescription: INodeTypeDescription = {
+ displayName: 'Lemlist',
+ name: 'lemlist',
+ icon: 'file:lemlist.svg',
+ group: ['transform'],
+ version: 2,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Consume the Lemlist API',
+ defaults: {
+ name: 'Lemlist',
+ },
+ inputs: [NodeConnectionType.Main],
+ outputs: [NodeConnectionType.Main],
+ credentials: [
+ {
+ name: 'lemlistApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Activity',
+ value: 'activity',
+ },
+ {
+ name: 'Campaign',
+ value: 'campaign',
+ },
+ {
+ name: 'Enrichment',
+ value: 'enrich',
+ },
+ {
+ name: 'Lead',
+ value: 'lead',
+ },
+ {
+ name: 'Team',
+ value: 'team',
+ },
+ {
+ name: 'Unsubscribe',
+ value: 'unsubscribe',
+ },
+ ],
+ default: 'activity',
+ },
+ ...activityOperations,
+ ...activityFields,
+ ...campaignOperations,
+ ...campaignFields,
+ ...enrichmentOperations,
+ ...enrichmentFields,
+ ...leadOperations,
+ ...leadFields,
+ ...teamOperations,
+ ...teamFields,
+ ...unsubscribeOperations,
+ ...unsubscribeFields,
+ ],
+};
+export class LemlistV2 implements INodeType {
+ description: INodeTypeDescription;
+
+ constructor(baseDescription: INodeTypeBaseDescription) {
+ this.description = {
+ ...baseDescription,
+ ...versionDescription,
+ };
+ }
+
+ methods = {
+ loadOptions: {
+ async getCampaigns(this: ILoadOptionsFunctions) {
+ const campaigns = await lemlistApiRequest.call(this, 'GET', '/campaigns');
+ return campaigns.map(({ _id, name }: { _id: string; name: string }) => ({
+ name,
+ value: _id,
+ }));
+ },
+ },
+ };
+
+ async execute(this: IExecuteFunctions) {
+ const items = this.getInputData();
+
+ const resource = this.getNodeParameter('resource', 0);
+ const operation = this.getNodeParameter('operation', 0);
+
+ let responseData;
+ const returnData: INodeExecutionData[] = [];
+
+ for (let i = 0; i < items.length; i++) {
+ try {
+ if (resource === 'activity') {
+ // *********************************************************************
+ // activity
+ // *********************************************************************
+
+ if (operation === 'getAll') {
+ // ----------------------------------
+ // activity: getAll
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#activities
+
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ const qs = {} as IDataObject;
+ const filters = this.getNodeParameter('filters', i);
+
+ if (!isEmpty(filters)) {
+ Object.assign(qs, filters);
+ }
+
+ if (returnAll) {
+ responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/activities', qs);
+ } else {
+ qs.limit = this.getNodeParameter('limit', i);
+ responseData = await lemlistApiRequest.call(this, 'GET', '/activities', {}, qs);
+ }
+ }
+ } else if (resource === 'campaign') {
+ // *********************************************************************
+ // campaign
+ // *********************************************************************
+
+ if (operation === 'getAll') {
+ // ----------------------------------
+ // campaign: getAll
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#32ab1bf9-9b2f-40ed-9bbd-0b8370fed3d9
+ const qs = {} as IDataObject;
+ const filters = this.getNodeParameter('filters', i);
+
+ if (!isEmpty(filters)) {
+ Object.assign(qs, filters);
+ }
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ if (returnAll) {
+ responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/campaigns', {});
+ } else {
+ qs.limit = this.getNodeParameter('limit', i);
+ responseData = await lemlistApiRequest.call(this, 'GET', '/campaigns', {}, qs);
+ }
+ } else if (operation === 'getStats') {
+ // ----------------------------------
+ // campaign: getStats
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#0b5cc72c-c1c8-47d0-a086-32b1b63522e3
+ const qs = {} as IDataObject;
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+
+ qs.startDate = this.getNodeParameter('startDate', i);
+ qs.endDate = this.getNodeParameter('endDate', i);
+ qs.timezone = this.getNodeParameter('timezone', i);
+ responseData = await lemlistApiRequest.call(
+ this,
+ 'GET',
+ `/campaigns/${campaignId}/stats`,
+ {},
+ qs,
+ );
+ }
+ } else if (resource === 'lead') {
+ // *********************************************************************
+ // lead
+ // *********************************************************************
+
+ if (operation === 'create') {
+ // ----------------------------------
+ // lead: create
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#add-a-lead-in-a-campaign
+
+ const qs = {} as IDataObject;
+ const additionalFields = this.getNodeParameter('additionalFields', i);
+
+ if (additionalFields.deduplicate !== undefined) {
+ qs.deduplicate = additionalFields.deduplicate;
+ }
+
+ const body = {} as IDataObject;
+
+ const remainingAdditionalFields = omit(additionalFields, 'deduplicate');
+
+ if (!isEmpty(remainingAdditionalFields)) {
+ Object.assign(body, remainingAdditionalFields);
+ }
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+ const email = this.getNodeParameter('email', i);
+ const endpoint = `/campaigns/${campaignId}/leads/${email}`;
+
+ responseData = await lemlistApiRequest.call(this, 'POST', endpoint, body, qs);
+ } else if (operation === 'delete') {
+ // ----------------------------------
+ // lead: delete
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#delete-a-lead-from-a-campaign
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+ const email = this.getNodeParameter('email', i);
+ const endpoint = `/campaigns/${campaignId}/leads/${email}`;
+ responseData = await lemlistApiRequest.call(
+ this,
+ 'DELETE',
+ endpoint,
+ {},
+ { action: 'remove' },
+ );
+ } else if (operation === 'get') {
+ // ----------------------------------
+ // lead: get
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#get-a-specific-lead-by-email
+
+ const email = this.getNodeParameter('email', i);
+ responseData = await lemlistApiRequest.call(this, 'GET', `/leads/${email}`);
+ } else if (operation === 'unsubscribe') {
+ // ----------------------------------
+ // lead: unsubscribe
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#unsubscribe-a-lead-from-a-campaign
+
+ const campaignId = this.getNodeParameter('campaignId', i);
+ const email = this.getNodeParameter('email', i);
+ const endpoint = `/campaigns/${campaignId}/leads/${email}`;
+ responseData = await lemlistApiRequest.call(this, 'DELETE', endpoint);
+ }
+ } else if (resource === 'team') {
+ // *********************************************************************
+ // team
+ // *********************************************************************
+
+ if (operation === 'get') {
+ // ----------------------------------
+ // team: get
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#team
+
+ responseData = await lemlistApiRequest.call(this, 'GET', '/team');
+ } else if (operation === 'getCredits') {
+ // ----------------------------------
+ // team: getCredits
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#c9af1cf3-8d3d-469e-a548-268b579d2cb3
+
+ responseData = await lemlistApiRequest.call(this, 'GET', '/team/credits');
+ }
+ } else if (resource === 'unsubscribe') {
+ // *********************************************************************
+ // unsubscribe
+ // *********************************************************************
+
+ if (operation === 'add') {
+ // ----------------------------------
+ // unsubscribe: Add
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#add-an-email-address-in-the-unsubscribes
+
+ const email = this.getNodeParameter('email', i);
+ responseData = await lemlistApiRequest.call(this, 'POST', `/unsubscribes/${email}`);
+ } else if (operation === 'delete') {
+ // ----------------------------------
+ // unsubscribe: delete
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#delete-an-email-address-from-the-unsubscribes
+
+ const email = this.getNodeParameter('email', i);
+ responseData = await lemlistApiRequest.call(this, 'DELETE', `/unsubscribes/${email}`);
+ } else if (operation === 'getAll') {
+ // ----------------------------------
+ // unsubscribe: getAll
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#list-all-unsubscribes
+
+ const returnAll = this.getNodeParameter('returnAll', i);
+
+ if (returnAll) {
+ responseData = await lemlistApiRequestAllItems.call(this, 'GET', '/unsubscribes', {});
+ } else {
+ const qs = {
+ limit: this.getNodeParameter('limit', i),
+ };
+ responseData = await lemlistApiRequest.call(this, 'GET', '/unsubscribes', {}, qs);
+ }
+ }
+ } else if (resource === 'enrich') {
+ // *********************************************************************
+ // enrichment
+ // *********************************************************************
+
+ if (operation === 'get') {
+ // ----------------------------------
+ // enrichment: get
+ // ----------------------------------
+
+ // https://developer.lemlist.com/#71b74cc3-8098-4389-b3c2-67a027df9407
+
+ const enrichId = this.getNodeParameter('enrichId', i);
+
+ responseData = await lemlistApiRequest.call(this, 'GET', `/enrich/${enrichId}`);
+ } else if (operation === 'enrichLead') {
+ // https://developer.lemlist.com/#fe2a52fc-fa73-46d0-8b7d-395d9653bfd5
+ const findEmail = this.getNodeParameter('findEmail', i);
+ const verifyEmail = this.getNodeParameter('verifyEmail', i);
+ const linkedinEnrichment = this.getNodeParameter('linkedinEnrichment', i);
+ const findPhone = this.getNodeParameter('findPhone', i);
+ const qs = {} as IDataObject;
+
+ qs.findEmail = findEmail;
+ qs.verifyEmail = verifyEmail;
+ qs.linkedinEnrichment = linkedinEnrichment;
+ qs.findPhone = findPhone;
+
+ const body = {} as IDataObject;
+
+ const leadId = this.getNodeParameter('leadId', i);
+ const endpoint = `/leads/${leadId}/enrich/`;
+
+ responseData = await lemlistApiRequest.call(this, 'POST', endpoint, body, qs);
+ } else if (operation === 'enrichPerson') {
+ // https://developer.lemlist.com/#4ba3d505-0bfa-4f36-8549-f3cb343786bf
+ const findEmail = this.getNodeParameter('findEmail', i);
+ const verifyEmail = this.getNodeParameter('verifyEmail', i);
+ const linkedinEnrichment = this.getNodeParameter('linkedinEnrichment', i);
+ const findPhone = this.getNodeParameter('findPhone', i);
+ const additionalFields = this.getNodeParameter('additionalFields', i);
+ const qs = {} as IDataObject;
+ if (!isEmpty(additionalFields)) {
+ Object.assign(qs, additionalFields);
+ }
+ qs.findEmail = findEmail;
+ qs.verifyEmail = verifyEmail;
+ qs.linkedinEnrichment = linkedinEnrichment;
+ qs.findPhone = findPhone;
+
+ const body = {} as IDataObject;
+
+ const endpoint = '/enrich/';
+
+ responseData = await lemlistApiRequest.call(this, 'POST', endpoint, body, qs);
+ }
+ }
+ } 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];
+ }
+}
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/ActivityDescription.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/ActivityDescription.ts
new file mode 100644
index 0000000000..98c06a1d53
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/ActivityDescription.ts
@@ -0,0 +1,301 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const activityOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ default: 'getAll',
+ options: [
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many activities',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: ['activity'],
+ },
+ },
+ },
+];
+
+export const activityFields: INodeProperties[] = [
+ // ----------------------------------
+ // activity: getAll
+ // ----------------------------------
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: {
+ show: {
+ resource: ['activity'],
+ operation: ['getAll'],
+ },
+ },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 5,
+ description: 'Max number of results to return',
+ typeOptions: {
+ minValue: 1,
+ maxValue: 1000,
+ },
+ displayOptions: {
+ show: {
+ resource: ['activity'],
+ operation: ['getAll'],
+ returnAll: [false],
+ },
+ },
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: ['activity'],
+ operation: ['getAll'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Campaign Name or ID',
+ name: 'campaignId',
+ type: 'options',
+ default: '',
+ typeOptions: {
+ loadOptionsMethod: 'getCampaigns',
+ },
+ description:
+ 'ID of the campaign to retrieve activity for. Choose from the list, or specify an ID using an expression.',
+ },
+ {
+ displayName: 'Is First',
+ name: 'isFirst',
+ type: 'boolean',
+ default: false,
+ },
+ {
+ displayName: 'Lead ID',
+ name: 'leadId',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ default: 'emailsOpened',
+ description: 'Type of activity to retrieve',
+ options: [
+ {
+ name: 'Aircall Created',
+ value: 'aircallCreated',
+ },
+ {
+ name: 'Aircall Done',
+ value: 'aircallDone',
+ },
+ {
+ name: 'Aircall Ended',
+ value: 'aircallEnded',
+ },
+ {
+ name: 'Aircall Interested',
+ value: 'aircallInterested',
+ },
+ {
+ name: 'Aircall Not Interested',
+ value: 'aircallNotInterested',
+ },
+ {
+ name: 'Api Done',
+ value: 'apiDone',
+ },
+ {
+ name: 'Api Failed',
+ value: 'apiFailed',
+ },
+ {
+ name: 'Api Interested',
+ value: 'apiInterested',
+ },
+ {
+ name: 'Api Not Interested',
+ value: 'apiNotInterested',
+ },
+ {
+ name: 'Attracted',
+ value: 'attracted',
+ },
+ {
+ name: 'Connection Issue',
+ value: 'connectionIssue',
+ },
+ {
+ name: 'Contacted',
+ value: 'contacted',
+ },
+ {
+ name: 'Custom Domain Errors',
+ value: 'customDomainErrors',
+ },
+ {
+ name: 'Emails Bounced',
+ value: 'emailsBounced',
+ },
+ {
+ name: 'Emails Clicked',
+ value: 'emailsClicked',
+ },
+ {
+ name: 'Emails Failed',
+ value: 'emailsFailed',
+ },
+ {
+ name: 'Emails Interested',
+ value: 'emailsInterested',
+ },
+ {
+ name: 'Emails Not Interested',
+ value: 'emailsNotInterested',
+ },
+ {
+ name: 'Emails Opened',
+ value: 'emailsOpened',
+ },
+ {
+ name: 'Emails Replied',
+ value: 'emailsReplied',
+ },
+ {
+ name: 'Emails Send Failed',
+ value: 'emailsSendFailed',
+ },
+ {
+ name: 'Emails Sent',
+ value: 'emailsSent',
+ },
+ {
+ name: 'Emails Unsubscribed',
+ value: 'emailsUnsubscribed',
+ },
+ {
+ name: 'Hooked',
+ value: 'hooked',
+ },
+ {
+ name: 'Interested',
+ value: 'interested',
+ },
+ {
+ name: 'Lemwarm Paused',
+ value: 'lemwarmPaused',
+ },
+ {
+ name: 'LinkedIn Interested',
+ value: 'linkedinInterested',
+ },
+ {
+ name: 'LinkedIn Invite Accepted',
+ value: 'linkedinInviteAccepted',
+ },
+ {
+ name: 'LinkedIn Invite Done',
+ value: 'linkedinInviteDone',
+ },
+ {
+ name: 'LinkedIn Invite Failed',
+ value: 'linkedinInviteFailed',
+ },
+ {
+ name: 'LinkedIn Not Interested',
+ value: 'linkedinNotInterested',
+ },
+ {
+ name: 'LinkedIn Replied',
+ value: 'linkedinReplied',
+ },
+ {
+ name: 'LinkedIn Send Failed',
+ value: 'linkedinSendFailed',
+ },
+ {
+ name: 'LinkedIn Sent',
+ value: 'linkedinSent',
+ },
+ {
+ name: 'LinkedIn Visit Done',
+ value: 'linkedinVisitDone',
+ },
+ {
+ name: 'LinkedIn Visit Failed',
+ value: 'linkedinVisitFailed',
+ },
+ {
+ name: 'LinkedIn Voice Note Done',
+ value: 'linkedinVoiceNoteDone',
+ },
+ {
+ name: 'LinkedIn Voice Note Failed',
+ value: 'linkedinVoiceNoteFailed',
+ },
+ {
+ name: 'Manual Interested',
+ value: 'manualInterested',
+ },
+ {
+ name: 'Manual Not Interested',
+ value: 'manualNotInterested',
+ },
+ {
+ name: 'Not Interested',
+ value: 'notInterested',
+ },
+ {
+ name: 'Opportunities Done',
+ value: 'opportunitiesDone',
+ },
+ {
+ name: 'Paused',
+ value: 'paused',
+ },
+ {
+ name: 'Resumed',
+ value: 'resumed',
+ },
+ {
+ name: 'Send Limit Reached',
+ value: 'sendLimitReached',
+ },
+ {
+ name: 'Skipped',
+ value: 'skipped',
+ },
+ {
+ name: 'Warmed',
+ value: 'warmed',
+ },
+ ],
+ },
+ {
+ displayName: 'Version',
+ name: 'version',
+ type: 'string',
+ default: 'v2',
+ },
+ ],
+ },
+];
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/CampaignDescription.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/CampaignDescription.ts
new file mode 100644
index 0000000000..169a1620d2
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/CampaignDescription.ts
@@ -0,0 +1,149 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const campaignOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ default: 'getAll',
+ options: [
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many campaigns',
+ },
+ {
+ name: 'Get Stats',
+ value: 'getStats',
+ action: 'Get campaign stats',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ },
+ },
+ },
+];
+
+export const campaignFields: INodeProperties[] = [
+ // ----------------------------------
+ // campaign: getAll
+ // ----------------------------------
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getAll'],
+ },
+ },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 5,
+ description: 'Max number of results to return',
+ typeOptions: {
+ minValue: 1,
+ maxValue: 1000,
+ },
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getAll'],
+ returnAll: [false],
+ },
+ },
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getAll'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Version',
+ name: 'version',
+ type: 'string',
+ default: 'v2',
+ },
+ ],
+ },
+ // ----------------------------------
+ // campaign: getStats
+ // ----------------------------------
+ {
+ displayName: 'Campaign Name or ID',
+ name: 'campaignId',
+ type: 'options',
+ required: true,
+ default: [],
+ typeOptions: {
+ loadOptionsMethod: 'getCampaigns',
+ },
+ description:
+ 'ID of the campaign to get stats for. Choose from the list, or specify an ID using an expression.',
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getStats'],
+ },
+ },
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ required: true,
+ placeholder: 'e.g. 2024-09-03 00:00:00Z',
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getStats'],
+ },
+ },
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'dateTime',
+ default: '',
+ placeholder: 'e.g. 2024-09-03 00:00:00Z',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getStats'],
+ },
+ },
+ },
+ {
+ displayName: 'Timezone',
+ name: 'timezone',
+ type: 'string',
+ default: '',
+ required: true,
+ placeholder: 'e.g. Europe/Paris',
+ displayOptions: {
+ show: {
+ resource: ['campaign'],
+ operation: ['getStats'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/EnrichmentDescription.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/EnrichmentDescription.ts
new file mode 100644
index 0000000000..da3c6e93b3
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/EnrichmentDescription.ts
@@ -0,0 +1,172 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const enrichmentOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ default: 'get',
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Fetches a previously completed enrichment',
+ },
+ {
+ name: 'Enrich Lead',
+ value: 'enrichLead',
+ action: 'Enrich a lead using an email or LinkedIn URL',
+ },
+ {
+ name: 'Enrich Person',
+ value: 'enrichPerson',
+ action: 'Enrich a person using an email or LinkedIn URL',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ },
+ },
+ },
+];
+
+export const enrichmentFields: INodeProperties[] = [
+ // ----------------------------------
+ // enrichment: get
+ // ----------------------------------
+ {
+ displayName: 'Enrichment ID',
+ name: 'enrichId',
+ type: 'string',
+ default: '',
+ required: true,
+ description: 'ID of the enrichment to retrieve',
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['get'],
+ },
+ },
+ },
+ // ----------------------------------
+ // enrichment: enrichLead
+ // ----------------------------------
+ {
+ displayName: 'Lead ID',
+ name: 'leadId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['enrichLead'],
+ },
+ },
+ },
+ {
+ displayName: 'Find Email',
+ name: 'findEmail',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['enrichLead', 'enrichPerson'],
+ },
+ },
+ },
+ {
+ displayName: 'Verify Email',
+ name: 'verifyEmail',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['enrichLead', 'enrichPerson'],
+ },
+ },
+ },
+ {
+ displayName: 'Linkedin Enrichment',
+ name: 'linkedinEnrichment',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['enrichLead', 'enrichPerson'],
+ },
+ },
+ },
+ {
+ displayName: 'Find Phone',
+ name: 'findPhone',
+ type: 'boolean',
+ default: false,
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['enrichLead', 'enrichPerson'],
+ },
+ },
+ },
+ // ----------------------------------
+ // enrichment: enrichPerson
+ // ----------------------------------
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: ['enrich'],
+ operation: ['enrichPerson'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Linkedin Url',
+ name: 'linkedinUrl',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Company Name',
+ name: 'companyName',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Company Domain',
+ name: 'companyDomain',
+ type: 'string',
+ default: '',
+ },
+ ],
+ },
+];
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts
new file mode 100644
index 0000000000..abb5b22bfc
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts
@@ -0,0 +1,281 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const leadOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ default: 'create',
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ action: 'Create a lead',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete a lead',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a lead',
+ },
+ {
+ name: 'Unsubscribe',
+ value: 'unsubscribe',
+ action: 'Unsubscribe a lead',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ },
+ },
+ },
+];
+
+export const leadFields: INodeProperties[] = [
+ // ----------------------------------
+ // lead: create
+ // ----------------------------------
+ {
+ displayName: 'Campaign Name or ID',
+ name: 'campaignId',
+ type: 'options',
+ required: true,
+ default: [],
+ typeOptions: {
+ loadOptionsMethod: 'getCampaigns',
+ },
+ description:
+ 'ID of the campaign to create the lead under. Choose from the list, or specify an ID using an expression.',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ description: 'Email of the lead to create',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['create'],
+ },
+ },
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['create'],
+ },
+ },
+ options: [
+ {
+ displayName: 'Company Name',
+ name: 'companyName',
+ type: 'string',
+ default: '',
+ description: 'Company name of the lead to create',
+ },
+ {
+ displayName: 'Company Domain',
+ name: 'companyDomain',
+ type: 'string',
+ default: '',
+ description: 'Company domain of the lead to create',
+ },
+ {
+ displayName: 'Deduplicate',
+ name: 'deduplicate',
+ type: 'boolean',
+ default: false,
+ description:
+ 'Whether to do not insert if this email is already present in another campaign',
+ },
+ {
+ displayName: 'Find Email',
+ name: 'findEmail',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to find verified email',
+ },
+ {
+ displayName: 'Find Phone',
+ name: 'findPhone',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to find phone number',
+ },
+ {
+ displayName: 'First Name',
+ name: 'firstName',
+ type: 'string',
+ default: '',
+ description: 'First name of the lead to create',
+ },
+ {
+ displayName: 'Icebreaker',
+ name: 'icebreaker',
+ type: 'string',
+ default: '',
+ description: 'Icebreaker of the lead to create',
+ },
+ {
+ displayName: 'Job Title',
+ name: 'jobTitle',
+ type: 'string',
+ default: '',
+ description: 'Job title of the lead to create',
+ },
+ {
+ displayName: 'Last Name',
+ name: 'lastName',
+ type: 'string',
+ default: '',
+ description: 'Last name of the lead to create',
+ },
+ {
+ displayName: 'Linkedin Enrichment',
+ name: 'linkedinEnrichment',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to run the LinkedIn enrichment',
+ },
+
+ {
+ displayName: 'LinkedIn URL',
+ name: 'linkedinUrl',
+ type: 'string',
+ default: '',
+ description: 'LinkedIn URL of the lead to create',
+ },
+ {
+ displayName: 'Phone',
+ name: 'phone',
+ type: 'string',
+ default: '',
+ description: 'Phone number of the lead to create',
+ },
+ {
+ displayName: 'Picture URL',
+ name: 'picture',
+ type: 'string',
+ default: '',
+ description: 'Picture URL of the lead to create',
+ },
+ {
+ displayName: 'Verify Email',
+ name: 'verifyEmail',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to verify existing email (debounce)',
+ },
+ ],
+ },
+
+ // ----------------------------------
+ // lead: delete
+ // ----------------------------------
+ {
+ displayName: 'Campaign Name or ID',
+ name: 'campaignId',
+ type: 'options',
+ required: true,
+ default: [],
+ typeOptions: {
+ loadOptionsMethod: 'getCampaigns',
+ },
+ description:
+ 'ID of the campaign to remove the lead from. Choose from the list, or specify an ID using an expression.',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['delete'],
+ },
+ },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ description: 'Email of the lead to delete',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['delete'],
+ },
+ },
+ },
+
+ // ----------------------------------
+ // lead: get
+ // ----------------------------------
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ description: 'Email of the lead to retrieve',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['get'],
+ },
+ },
+ },
+
+ // ----------------------------------
+ // lead: unsubscribe
+ // ----------------------------------
+ {
+ displayName: 'Campaign Name or ID',
+ name: 'campaignId',
+ type: 'options',
+ required: true,
+ default: [],
+ typeOptions: {
+ loadOptionsMethod: 'getCampaigns',
+ },
+ description:
+ 'ID of the campaign to unsubscribe the lead from. Choose from the list, or specify an ID using an expression.',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['unsubscribe'],
+ },
+ },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ description: 'Email of the lead to unsubscribe',
+ displayOptions: {
+ show: {
+ resource: ['lead'],
+ operation: ['unsubscribe'],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/TeamDescription.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/TeamDescription.ts
new file mode 100644
index 0000000000..51b8da1189
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/TeamDescription.ts
@@ -0,0 +1,34 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const teamOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ default: 'get',
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ action: 'Get a team',
+ },
+ {
+ name: 'Get Credits',
+ value: 'getCredits',
+ action: 'Get a team',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: ['team'],
+ },
+ },
+ },
+];
+
+export const teamFields: INodeProperties[] = [
+ // ----------------------------------
+ // team: get
+ // ----------------------------------
+];
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/UnsubscribeDescription.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/UnsubscribeDescription.ts
new file mode 100644
index 0000000000..fc87c69d1d
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/UnsubscribeDescription.ts
@@ -0,0 +1,106 @@
+import type { INodeProperties } from 'n8n-workflow';
+
+export const unsubscribeOperations: INodeProperties[] = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ default: 'add',
+ options: [
+ {
+ name: 'Add',
+ value: 'add',
+ action: 'Add an email to an unsubscribe list',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ action: 'Delete an email from an unsubscribe list',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ action: 'Get many unsubscribed emails',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: ['unsubscribe'],
+ },
+ },
+ },
+];
+
+export const unsubscribeFields: INodeProperties[] = [
+ // ----------------------------------
+ // unsubscribe: add
+ // ----------------------------------
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ description: 'Email to add to the unsubscribes',
+ displayOptions: {
+ show: {
+ resource: ['unsubscribe'],
+ operation: ['add'],
+ },
+ },
+ },
+
+ // ----------------------------------
+ // unsubscribe: delete
+ // ----------------------------------
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string',
+ placeholder: 'name@email.com',
+ default: '',
+ description: 'Email to delete from the unsubscribes',
+ displayOptions: {
+ show: {
+ resource: ['unsubscribe'],
+ operation: ['delete'],
+ },
+ },
+ },
+
+ // ----------------------------------
+ // unsubscribe: getAll
+ // ----------------------------------
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: {
+ show: {
+ resource: ['unsubscribe'],
+ operation: ['getAll'],
+ },
+ },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 5,
+ description: 'Max number of results to return',
+ typeOptions: {
+ minValue: 1,
+ maxValue: 1000,
+ },
+ displayOptions: {
+ show: {
+ resource: ['unsubscribe'],
+ operation: ['getAll'],
+ returnAll: [false],
+ },
+ },
+ },
+];
diff --git a/packages/nodes-base/nodes/Lemlist/v2/descriptions/index.ts b/packages/nodes-base/nodes/Lemlist/v2/descriptions/index.ts
new file mode 100644
index 0000000000..42ccef0ec6
--- /dev/null
+++ b/packages/nodes-base/nodes/Lemlist/v2/descriptions/index.ts
@@ -0,0 +1,6 @@
+export * from './ActivityDescription';
+export * from './CampaignDescription';
+export * from './EnrichmentDescription';
+export * from './LeadDescription';
+export * from './TeamDescription';
+export * from './UnsubscribeDescription';