Skip to content

Conversation

@nicolasmondain
Copy link

Summary

This PR updates the LemlistV2 node to cover all current Lemlist API endpoints based on the official documentation at https://developer.lemlist.com.

New Resources Added (8)

  • Company - List companies, add/get notes
  • Contact - Get contact by ID/email, list all contacts
  • Database - Get filters, get people/companies schema, search people/companies
  • Inbox - Get conversations, get messages, send email/LinkedIn/WhatsApp messages
  • Schedule - CRUD operations and campaign association
  • Sequence - List sequences, create/update/delete steps
  • Task - Create, list, update, and ignore tasks
  • Webhook - Create, delete, and list webhooks

Updated Existing Resources

  • Campaign - Added: create, get, update, pause, getReports, exportLeads
  • Lead - Added: getAll, update, pause, resume, markInterested, markNotInterested
  • Team - Added: getSenders
  • Unsubscribe - Added: get, export
  • Enrichment - Added: bulkEnrich

Database Search Operations

The database search operations (searchPeople, searchCompanies) now use the correct API format with:

  • Filters array with filterId, in, and out properties
  • page and size parameters
  • excludes array for excluding properties from results
  • New getFilters operation to retrieve available filter IDs

Tests

  • Added workflow tests with mock API responses
  • Test files: Lemlist.node.test.ts, apiResponses.ts, workflow.json

Related Linear tickets, Github issues, and Community forum posts

Review / Merge checklist

  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with release/backport (if the PR is an urgent fix that needs to be backported)

…erage

Add 8 new resources: Company, Contact, Database, Inbox, Schedule, Sequence, Task, Webhook

Update existing resources with new operations:
- Campaign: create, get, update, pause, getReports, exportLeads
- Lead: getAll, update, pause, resume, markInterested, markNotInterested
- Team: getSenders
- Unsubscribe: get, export
- Enrichment: bulkEnrich

Add workflow tests with mock API responses
…ter structure

- Update searchPeople and searchCompanies to use proper filter array format
- Each filter now has filterId, in (include values), and out (exclude values)
- Add page, size, and excludes options as per API documentation
- Use fixedCollection for better UX when adding multiple filters
…red fields

- Add seed parameter for consistent pagination results
- Move page, size, excludes to top-level fields (not in options)
- Always include in/out arrays in filters (empty array if not set)
- Use consistent filterValues name for fixedCollection
- Body now matches expected format:
  {filters: [{filterId, in: [], out: []}], seed, page, size, excludes: []}
- Add GET /database/filters endpoint to retrieve available filters
- Each filter returns: filterId, description, mode, type, helper, values
- Update filter descriptions to reference the new getFilters operation
- Clarify that filters with 'leads' mode work for people queries
- Clarify that filters with 'companies' mode work for company queries
@CLAassistant
Copy link

CLAassistant commented Jan 8, 2026

CLA assistant check
All committers have signed the CLA.

@n8n-assistant n8n-assistant bot added community Authored by a community member node/improvement New feature or request in linear Issue or PR has been created in Linear for internal review labels Jan 8, 2026
@n8n-assistant
Copy link
Contributor

n8n-assistant bot commented Jan 8, 2026

Hey @nicolasmondain,

Thank you for your contribution. We appreciate the time and effort you’ve taken to submit this pull request.

Before we can proceed, please ensure the following:
• Tests are included for any new functionality, logic changes or bug fixes.
• The PR aligns with our contribution guidelines.

Regarding new nodes:
We no longer accept new nodes directly into the core codebase. Instead, we encourage contributors to follow our Community Node Submission Guide to publish nodes independently.

If your node integrates with an AI service that you own or represent, please email [email protected] and we will be happy to discuss the best approach.

About review timelines:
This PR has been added to our internal tracker as "GHC-6244". While we plan to review it, we are currently unable to provide an exact timeframe. Our goal is to begin reviews within a month, but this may change depending on team priorities. We will reach out when the review begins.

Thank you again for contributing to n8n.

- Sort step types alphabetically in SequenceDescription
- Sort webhook events alphabetically in WebhookDescription
@nicolasmondain nicolasmondain marked this pull request as ready for review January 8, 2026 20:57
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 18 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts">

<violation number="1" location="packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts:334">
P2: Missing `required: true` for campaignId when scope is 'campaign'. Other campaignId fields in this file (create, delete, update, unsubscribe) are marked as required. Without this, users could select 'Campaign' scope but fail to provide a campaign ID, leading to API errors.</violation>

<violation number="2" location="packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts:598">
P2: Missing `required: true` for campaignId when scope is 'campaign'. Consistent with other campaignId fields in this file, this should be required to prevent API errors when users select 'Campaign' scope.</violation>
</file>

<file name="packages/nodes-base/nodes/Lemlist/test/Lemlist.node.test.ts">

<violation number="1" location="packages/nodes-base/nodes/Lemlist/test/Lemlist.node.test.ts:93">
P2: Empty test body - this test will always pass without testing anything. The nock mock set up in `beforeEach` will never be called. Either implement the test using `testHarness.setupTests()` pattern (like in the Slack node tests) or remove these placeholder tests until they're implemented.</violation>
</file>

<file name="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts">

<violation number="1" location="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts:238">
P2: Rule violated: **Prefer Typeguards over Type casting**

Avoid inline type casting before method calls. Use a type guard or validate the type before calling `.split()`. If `additionalFields.labels` is not a string, this will cause a runtime error.</violation>

<violation number="2" location="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts:324">
P2: Rule violated: **Prefer Typeguards over Type casting**

Avoid inline type casting before method calls. Use a type guard or validate the type before calling `.split()` to prevent potential runtime errors.</violation>

<violation number="3" location="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts:1218">
P2: Rule violated: **Prefer Typeguards over Type casting**

Avoid inline type casting before method calls. Use `typeof filter.in === 'string'` as the guard instead of just truthy check to safely narrow the type.</violation>

<violation number="4" location="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts:1219">
P2: Rule violated: **Prefer Typeguards over Type casting**

Avoid inline type casting before method calls. Use `typeof filter.out === 'string'` as the guard instead of just truthy check to safely narrow the type.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

{
name: 'Campaign',
value: 'campaign',
description: 'Mark as not interested for a specific campaign',
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing required: true for campaignId when scope is 'campaign'. Consistent with other campaignId fields in this file, this should be required to prevent API errors when users select 'Campaign' scope.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts, line 598:

<comment>Missing `required: true` for campaignId when scope is 'campaign'. Consistent with other campaignId fields in this file, this should be required to prevent API errors when users select 'Campaign' scope.</comment>

<file context>
@@ -243,6 +283,347 @@ export const leadFields: INodeProperties[] = [
+			{
+				name: 'Campaign',
+				value: 'campaign',
+				description: 'Mark as not interested for a specific campaign',
+			},
+		],
</file context>

✅ Addressed in 7d60da5

Comment on lines +334 to +337
displayName: 'Campaign Name or ID',
name: 'campaignId',
type: 'options',
default: '',
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing required: true for campaignId when scope is 'campaign'. Other campaignId fields in this file (create, delete, update, unsubscribe) are marked as required. Without this, users could select 'Campaign' scope but fail to provide a campaign ID, leading to API errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/descriptions/LeadDescription.ts, line 334:

<comment>Missing `required: true` for campaignId when scope is 'campaign'. Other campaignId fields in this file (create, delete, update, unsubscribe) are marked as required. Without this, users could select 'Campaign' scope but fail to provide a campaign ID, leading to API errors.</comment>

<file context>
@@ -243,6 +283,347 @@ export const leadFields: INodeProperties[] = [
+		},
+		options: [
+			{
+				displayName: 'Campaign Name or ID',
+				name: 'campaignId',
+				type: 'options',
</file context>
Suggested change
displayName: 'Campaign Name or ID',
name: 'campaignId',
type: 'options',
default: '',
displayName: 'Campaign Name or ID',
name: 'campaignId',
type: 'options',
required: true,
default: '',

✅ Addressed in 7d60da5

@@ -0,0 +1,522 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Empty test body - this test will always pass without testing anything. The nock mock set up in beforeEach will never be called. Either implement the test using testHarness.setupTests() pattern (like in the Slack node tests) or remove these placeholder tests until they're implemented.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/test/Lemlist.node.test.ts, line 93:

<comment>Empty test body - this test will always pass without testing anything. The nock mock set up in `beforeEach` will never be called. Either implement the test using `testHarness.setupTests()` pattern (like in the Slack node tests) or remove these placeholder tests until they're implemented.</comment>

<file context>
@@ -0,0 +1,522 @@
+			nock.cleanAll();
+		});
+
+		it('should get a specific campaign', async () => {
+			// Test would be similar pattern
+		});
</file context>

✅ Addressed in 7d60da5

const filterObj: IDataObject = {
filterId: filter.filterId,
in: filter.in ? (filter.in as string).split(',').map((v) => v.trim()) : [],
out: filter.out ? (filter.out as string).split(',').map((v) => v.trim()) : [],
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Prefer Typeguards over Type casting

Avoid inline type casting before method calls. Use typeof filter.out === 'string' as the guard instead of just truthy check to safely narrow the type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts, line 1219:

<comment>Avoid inline type casting before method calls. Use `typeof filter.out === 'string'` as the guard instead of just truthy check to safely narrow the type.</comment>

<file context>
@@ -389,6 +661,609 @@ export class LemlistV2 implements INodeType {
+								const filterObj: IDataObject = {
+									filterId: filter.filterId,
+									in: filter.in ? (filter.in as string).split(',').map((v) => v.trim()) : [],
+									out: filter.out ? (filter.out as string).split(',').map((v) => v.trim()) : [],
+								};
+								filtersArray.push(filterObj);
</file context>
Suggested change
out: filter.out ? (filter.out as string).split(',').map((v) => v.trim()) : [],
out: typeof filter.out === 'string' ? filter.out.split(',').map((v) => v.trim()) : [],

✅ Addressed in 7d60da5

for (const filter of filtersInput.filterValues as IDataObject[]) {
const filterObj: IDataObject = {
filterId: filter.filterId,
in: filter.in ? (filter.in as string).split(',').map((v) => v.trim()) : [],
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Prefer Typeguards over Type casting

Avoid inline type casting before method calls. Use typeof filter.in === 'string' as the guard instead of just truthy check to safely narrow the type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts, line 1218:

<comment>Avoid inline type casting before method calls. Use `typeof filter.in === 'string'` as the guard instead of just truthy check to safely narrow the type.</comment>

<file context>
@@ -389,6 +661,609 @@ export class LemlistV2 implements INodeType {
+							for (const filter of filtersInput.filterValues as IDataObject[]) {
+								const filterObj: IDataObject = {
+									filterId: filter.filterId,
+									in: filter.in ? (filter.in as string).split(',').map((v) => v.trim()) : [],
+									out: filter.out ? (filter.out as string).split(',').map((v) => v.trim()) : [],
+								};
</file context>
Suggested change
in: filter.in ? (filter.in as string).split(',').map((v) => v.trim()) : [],
in: typeof filter.in === 'string' ? filter.in.split(',').map((v) => v.trim()) : [],

✅ Addressed in 7d60da5

body.name = updateFields.name;
}
if (updateFields.labels) {
body.labels = (updateFields.labels as string).split(',').map((l) => l.trim());
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Prefer Typeguards over Type casting

Avoid inline type casting before method calls. Use a type guard or validate the type before calling .split() to prevent potential runtime errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts, line 324:

<comment>Avoid inline type casting before method calls. Use a type guard or validate the type before calling `.split()` to prevent potential runtime errors.</comment>

<file context>
@@ -196,7 +280,91 @@ export class LemlistV2 implements INodeType {
+							body.name = updateFields.name;
+						}
+						if (updateFields.labels) {
+							body.labels = (updateFields.labels as string).split(',').map((l) => l.trim());
+						}
+
</file context>
Suggested change
body.labels = (updateFields.labels as string).split(',').map((l) => l.trim());
const labels = updateFields.labels;
if (typeof labels === 'string') {
body.labels = labels.split(',').map((l) => l.trim());
}

✅ Addressed in 7d60da5

const body: IDataObject = { name };

if (additionalFields.labels) {
body.labels = (additionalFields.labels as string).split(',').map((l) => l.trim());
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rule violated: Prefer Typeguards over Type casting

Avoid inline type casting before method calls. Use a type guard or validate the type before calling .split(). If additionalFields.labels is not a string, this will cause a runtime error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts, line 238:

<comment>Avoid inline type casting before method calls. Use a type guard or validate the type before calling `.split()`. If `additionalFields.labels` is not a string, this will cause a runtime error.</comment>

<file context>
@@ -160,12 +224,33 @@ export class LemlistV2 implements INodeType {
+						const body: IDataObject = { name };
+
+						if (additionalFields.labels) {
+							body.labels = (additionalFields.labels as string).split(',').map((l) => l.trim());
+						}
+
</file context>
Suggested change
body.labels = (additionalFields.labels as string).split(',').map((l) => l.trim());
const labels = additionalFields.labels;
if (typeof labels === 'string') {
body.labels = labels.split(',').map((l) => l.trim());
}

✅ Addressed in 7d60da5

- Add required: true for campaignId in markInterested and markNotInterested
- Replace type casts with type guards in LemlistV2.node.ts
- Rewrite tests using it.todo() instead of empty test bodies
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts">

<violation number="1" location="packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts:1219">
P2: Empty string handling changed: `''` now produces `['']` instead of `[]`. Consider filtering out empty values to maintain previous behavior and avoid sending empty string elements to the API:
```javascript
filter.in.split(',').map((v) => v.trim()).filter(Boolean)
```</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

const filterObj: IDataObject = {
filterId: filter.filterId,
in:
typeof filter.in === 'string' ? filter.in.split(',').map((v) => v.trim()) : [],
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Empty string handling changed: '' now produces [''] instead of []. Consider filtering out empty values to maintain previous behavior and avoid sending empty string elements to the API:

filter.in.split(',').map((v) => v.trim()).filter(Boolean)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/LemlistV2.node.ts, line 1219:

<comment>Empty string handling changed: `''` now produces `['']` instead of `[]`. Consider filtering out empty values to maintain previous behavior and avoid sending empty string elements to the API:
```javascript
filter.in.split(',').map((v) => v.trim()).filter(Boolean)
```</comment>

<file context>
@@ -1215,8 +1215,12 @@ export class LemlistV2 implements INodeType {
-									in: filter.in ? (filter.in as string).split(',').map((v) => v.trim()) : [],
-									out: filter.out ? (filter.out as string).split(',').map((v) => v.trim()) : [],
+									in:
+										typeof filter.in === 'string' ? filter.in.split(',').map((v) => v.trim()) : [],
+									out:
+										typeof filter.out === 'string'
</file context>
Suggested change
typeof filter.in === 'string' ? filter.in.split(',').map((v) => v.trim()) : [],
typeof filter.in === 'string'
? filter.in.split(',').map((v) => v.trim()).filter(Boolean)
: [],

✅ Addressed in 76acd32

Add .filter(Boolean) to prevent empty strings from producing [''] instead of []
@nicolasmondain
Copy link
Author

@cubic-dev-ai
#24066 (review)

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Jan 8, 2026

@cubic-dev-ai
#24066 (review)

@nicolasmondain I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 18 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/nodes-base/nodes/Lemlist/v2/descriptions/TaskDescription.ts">

<violation number="1" location="packages/nodes-base/nodes/Lemlist/v2/descriptions/TaskDescription.ts:77">
P2: The `status` field has `default: ''` but the options only include 'pending' and 'done'. This mismatch means the dropdown will have an invalid default value when the user adds this field. Either set `default: 'pending'` to use a valid option, or add an empty placeholder option if the API supports omitting the status.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

name: 'entityId',
type: 'string',
required: true,
default: '',
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The status field has default: '' but the options only include 'pending' and 'done'. This mismatch means the dropdown will have an invalid default value when the user adds this field. Either set default: 'pending' to use a valid option, or add an empty placeholder option if the API supports omitting the status.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/nodes-base/nodes/Lemlist/v2/descriptions/TaskDescription.ts, line 77:

<comment>The `status` field has `default: ''` but the options only include 'pending' and 'done'. This mismatch means the dropdown will have an invalid default value when the user adds this field. Either set `default: 'pending'` to use a valid option, or add an empty placeholder option if the API supports omitting the status.</comment>

<file context>
@@ -0,0 +1,312 @@
+		name: 'entityId',
+		type: 'string',
+		required: true,
+		default: '',
+		description: 'ID of the contact or lead to associate the task with',
+		displayOptions: {
</file context>
Suggested change
default: '',
default: 'pending',

✅ Addressed in d0564ff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Authored by a community member in linear Issue or PR has been created in Linear for internal review node/improvement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants