feat: Lemlist integration — personalized cold email (issue #20)#75
Open
jackulau wants to merge 1 commit into
Open
feat: Lemlist integration — personalized cold email (issue #20)#75jackulau wants to merge 1 commit into
jackulau wants to merge 1 commit into
Conversation
Adds a Lemlist cold-email integration with 6 operations backed by 57 unit
tests:
- lemlistAddLeadNode: POST /campaigns/{id}/leads/
- lemlistGetCampaignsNode: GET /campaigns?version=v2
- lemlistGetActivityNode: GET /activities?version=v2
- lemlistPauseLeadNode: POST /leads/pause/{leadId}
- lemlistResumeLeadNode: POST /leads/start/{leadId}
- lemlistMarkAsInterestedNode: POST /campaigns/{id}/leads/{leadIdOrEmail}/interested
Also includes lemlistCredential (apiKey) and Zod schemas for all inputs/outputs.
Auth uses HTTP Basic with empty username + API key as password (Lemlist's
real auth scheme, not the simplified example in the issue). All HTTP goes
through the shared fetchWithRetry helper for retries/timeouts/rate-limit
handling — nodes stay pure per the architectural decision in CLAUDE.md.
URL-encoding of path identifiers (leadId, campaignId, leadIdOrEmail) uses
encodeURIComponent, with test coverage for special characters including
email-form leads and characters like '+' and '/'.
Drive-by: fixes a pre-existing broken import path in
packages/nodes/src/integrations/google-sheets/googleSheets.ts
('../types/credentials.js' -> '@jam-nodes/core') that was blocking
package-root module loads and therefore blocking cross-package export
verification for the new Lemlist tests.
Tests: 252 passing (+57 new), 0 regressions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #20
Overview
Adds a Lemlist cold-email integration to
@jam-nodes/nodeswith six operations, an API-key credential, fully-typed Zod input/output schemas, and 57 unit tests. Follows the existingdevtointegration pattern exactly —defineApiKeyCredential,defineNode, sharedfetchWithRetry, colocated__tests__/.What's in here
Six nodes
lemlistAddLeadNode/campaigns/{campaignId}/leads/lemlistGetCampaignsNode/campaigns?version=v2lemlistGetActivityNode/activities?version=v2lemlistPauseLeadNode/leads/pause/{leadId}[?campaignId=]lemlistResumeLeadNode/leads/start/{leadId}[?campaignId=]lemlistMarkAsInterestedNode/campaigns/{campaignId}/leads/{leadIdOrEmail}/interestedEach node is defined in its own file under
packages/nodes/src/integrations/lemlist/, registered in the localindex.ts, the integrations barrel (integrations/index.ts), the package root (src/index.ts), and thebuiltInNodesarray.Credential
lemlistCredentialis defined viadefineApiKeyCredentialwith a Zod schema ({ apiKey: z.string().min(1) }).NodeCredentialsin@jam-nodes/corewas extended withlemlist?: { apiKey: string }so executors access credentials type-safely viacontext.credentials?.lemlist?.apiKey.Authentication
Lemlist's documented auth scheme is HTTP Basic with an empty username and the API key as the password — not a bearer token or raw header (the issue's example credential snippet was a simplification). A shared
buildLemlistAuthHeader()helper inutils.tscomputes\Basic ${base64(':' + apiKey)}`at request time. The credential definition'sauthenticate.properties` records the header name for UI/documentation purposes; the runtime encoding happens in the helper.Zod schemas
schemas.tsdefines three shared response schemas (LemlistLeadSchema,LemlistCampaignSchema,LemlistActivitySchema) and one input/output pair per node. Response-shape fields that Lemlist may omit use.nullable().optional()so we stay permissive without resorting toz.any()oras any(both forbidden perCLAUDE.md). All outputs are validated withLemlistXSchema.parse(...)before returning.Tests — 57 new, all passing
packages/nodes/src/integrations/lemlist/__tests__/lemlist.test.tscovers:buildLemlistAuthHeaderencoding round-trip (decode the base64 portion and assert it equals':<key>'),buildLemlistHeadersreturns bothAuthorizationandContent-Type,LEMLIST_API_BASEconstantmark-as-interested, special characters forpause-lead/resume-lead), optional query param forwarding, response normalization@jam-nodes/nodesroot to prove all six nodes andlemlistCredentialare exported, and thatbuiltInNodescontains all six:test-api-key, 429-then-200 retry provingfetchWithRetryis wired in on every nodeDrive-by fix
packages/nodes/src/integrations/google-sheets/googleSheets.tshad a pre-existing broken import path ('../types/credentials.js'— a file that does not exist) that prevented any code path from dynamically loading the package root. Corrected to'@jam-nodes/core'. This was required to enable the cross-package export tests to load the rootindex.tswithout crashing, and as a side-effect reduces the project's pre-existing typecheck error count from 18 to 17.Cross-cutting concerns / architectural compliance
Per the repo's
CLAUDE.mdand the decisions in issue #37 / PR #39:fetchWithRetryhelper, which handles 429 backoff (honoringretry-after), 5xx retries with exponential backoff, auth-error short-circuits, and configurable timeouts.z.any()oras anyanywhere in the new code.Test results
Baseline: 195 passing (main). New total: 252 (+57 lemlist). Zero regressions in any pre-existing test file.
Typecheck note
Pre-existing
tsc --noEmiterrors inapify/,google-sheets/(unrelated to my changes), andslack/(NodeCredentials.slacknot yet added to the core types) are still present. None of them are in any file I touched. Baseline: 18 errors → this branch: 17 errors (one fewer, from the google-sheets import fix above). Zero new errors introduced.Files changed
Added (11):
packages/nodes/src/integrations/lemlist/credentials.tspackages/nodes/src/integrations/lemlist/utils.tspackages/nodes/src/integrations/lemlist/schemas.tspackages/nodes/src/integrations/lemlist/add-lead.tspackages/nodes/src/integrations/lemlist/get-campaigns.tspackages/nodes/src/integrations/lemlist/get-activity.tspackages/nodes/src/integrations/lemlist/pause-lead.tspackages/nodes/src/integrations/lemlist/resume-lead.tspackages/nodes/src/integrations/lemlist/mark-as-interested.tspackages/nodes/src/integrations/lemlist/index.tspackages/nodes/src/integrations/lemlist/__tests__/lemlist.test.tsModified (4):
packages/core/src/types/node.ts— addedlemlistentry toNodeCredentialspackages/nodes/src/integrations/index.ts— Lemlist export blockpackages/nodes/src/index.ts— value exports, type exports, import block,builtInNodesarraypackages/nodes/src/integrations/google-sheets/googleSheets.ts— fix pre-existing broken import pathAcceptance Criteria (from issue #20)