feat(smartlead): add Smartlead integration - #6352
Conversation
Adds a Smartlead block with 22 tools covering campaigns, sequences, leads,
analytics, and webhooks.
Every request path, parameter, enum, and response mapping was verified against
the live Smartlead API rather than its documentation, which proved unreliable:
- `POST /campaigns/new` (documented) 404s; the real path is `/campaigns/create`
- `GET /campaigns/{id}` and `/sequences` return bare payloads, not the
documented `{success, data}` envelopes
- `/statistics` returns paginated per-email rows, not the documented aggregate
- `POST /campaigns/{id}/leads` returns import counters under entirely
different field names than documented
- documented `/leads/{id}`, `/top-level-analytics`, `/all-leads-activities`,
`/lead-lists/`, and `/lead-tags/` all 404
Enum values (campaign status, track settings, stop-lead settings, webhook event
types, engagement status) were probed value-by-value against the API.
Notes on the API's shape, encoded in the mappers:
- string-encoded numbers (`total_leads: "1"`, `sent_count: "0"`) are normalized
to numbers so a field never changes type between operations
- `seq_delay_details` is read as `delayInDays` but written as `delay_in_days`
- webhook writes echo `event_type_map`/`category_id_map` objects while the list
endpoint returns `event_types`/`categories` arrays; both map to arrays
- `track_settings` reads back in a vocabulary it will not accept on write
Statistics rows and lead message-history entries pass through unmapped: no
account could produce a non-empty sample, so no field names were invented.
Email-account tools and a webhook trigger are omitted for the same reason.
Adds a `smartlead-errors` extractor since the API's 400s put the useful text in
`message` while `error` is only "Bad Request".
…indings
Grows the block from 22 to 47 tools and fixes every defect found in review.
New tools (all executed against the live API end to end):
campaign email accounts (list/add/remove), duplicate, delete, CSV lead export,
webhook delete + delivery summary, lead + mailbox statistics, top-level
analytics by date, lead activities, get lead by id, unsubscribe from campaign,
unsubscribe globally, mark complete, delete from campaign, master-inbox
replies, lead lists (list/get/create/update/delete), email accounts, clients.
The endpoint inventory was rebuilt by extracting method+path from all 212
reference pages, which corrected several earlier conclusions: get-lead-by-id is
`/leads/{id}` (not under `/campaigns/`), lead lists are `/lead-list/`
(singular), and lead activities are `/campaigns/all-leads-activities` with no
campaign segment. More documented paths that 404 in reality: lead tags at
`/crm/leads/tags`, and webhook delete at `/campaigns/{id}/webhooks/{id}` —
deletion actually takes the id in the body.
Shapes the docs got wrong again, caught live: `GET /leads/{id}` wraps the lead
in a single-element `data` array; `DELETE .../leads/{id}` answers with the bare
string `success`, not JSON; duplicate returns `newCampaignId`; create/update
lead list take `listName`, and mark-complete takes `campaign_lead_map_id` where
its siblings take `lead.id`.
Review fixes:
- get_campaign, get_campaign_analytics and get_lead_by_email reported an
all-null success for a missing resource, because Smartlead answers HTTP 200
with `{}` (or an empty body) instead of 404. They now fail closed.
- update_campaign_settings silently reset stop_lead_settings and
send_as_plain_text: their dropdown defaults are materialized at block
creation, so every settings update carried them. Both now default to
"Leave unchanged".
- Malformed JSON in Leads/Sequences/Custom Fields resolved to `undefined`,
which overwrote the raw string the executor falls back on and dropped the
field silently. Parsing now raises, and is scoped to the operation that
consumes the field so a stale hidden value cannot fail an unrelated one.
- The four documented import overrides (block/unsubscribe/duplicate/bounce
lists) had no field, so the block's own skill instructions were unexecutable.
- leadId did not distinguish lead.id from campaign_lead_map_id; passing the
latter 404s, and list_campaign_leads surfaces it first.
- Path ids are trimmed and escaped; dead code and a hand-rolled id mapper removed.
Unverified and called out rather than guessed: add/remove email accounts to a
campaign (no mailbox could be connected, so only their error shape was seen),
and the row shapes for statistics, message history, inbox replies, email
accounts and clients — every one of those collections was empty on the
verification account, so their rows pass through unmapped.
…tion
Three tools sent a parameter Smartlead's validator rejects outright with 400,
so the affected operations failed whenever the field was filled in:
- get_campaign_lead_statistics paginated with `skip`; the endpoint accepts
`offset` and only echoes it back as `skip`.
- list_lead_activities and list_inbox_replies both sent a campaign filter.
`campaign_id`, `campaignId`, `campaign_ids` and `email_campaign_id` are all
rejected, so the filter is gone rather than advertised and broken.
mark_lead_complete reported `next_sequence: null` on every call, including when
a step remained: `status.nextSequence` is an object, not a number. It now maps
to `next_sequence_id` and `next_sequence_delay_in_days` — verified live
returning step 10093171 rather than null.
get_lead_by_id reused the by-email mapper, so it always claimed the lead belongs
to zero campaigns; `GET /leads/{id}` omits `lead_campaign_data` entirely. It now
declares the narrower shape it actually returns.
A stale advanced `clientId` leaked into list_email_accounts: advanced subblocks
serialize without evaluating their condition, and that tool consumes `clientId`
while sitting outside its condition list. The field is now offered for that
operation too, so the value is visible wherever it is sent.
Two dropdowns had defaults that act on their own. `status` defaulted to PAUSED,
so choosing Update Campaign Status and never opening the dropdown paused the
campaign; it now requires an explicit choice. `pauseLead` sent `false` on every
categorization, which risks resuming a paused lead; it now defaults to leaving
the state alone.
Also counts CSV export rows with a quote-aware scan so a newline inside a name,
location, or custom field no longer inflates the count, and fills in the block
output declarations for the fields the 47 tools actually return.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview Includes a The diff shown wires docs/catalog: a branded Reviewed by Cursor Bugbot for commit d7662d9. Configure here. |
…-integration # Conflicts: # apps/sim/tools/generated/tool-ids.ts # apps/sim/tools/generated/tool-metadata.ts # apps/sim/tools/generated/tool-outputs.ts
|
@cursor review |
Greptile SummaryThe PR adds a Smartlead integration spanning campaign, sequence, lead, account, analytics, inbox, lead-list, and webhook operations.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/blocks/blocks/smartlead.ts | Defines the Smartlead block’s operation selector, input controls, parameter transformations, and workflow output contract. |
| apps/sim/tools/smartlead/utils.ts | Centralizes Smartlead constants and response normalization, including nullable numeric conversion. |
| apps/sim/tools/smartlead/mark_lead_complete.ts | Correctly preserves zero-day next-sequence delays while normalizing absent or invalid values to null. |
| apps/sim/tools/error-extractors.ts | Adds extraction of useful Smartlead error messages from API error responses. |
| apps/docs/content/docs/en/integrations/smartlead.mdx | Documents the Smartlead operations with concrete enum values and generated input/output contracts. |
| apps/sim/tools/registry.ts | Registers the new Smartlead tools for execution. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Workflow[Workflow Smartlead block] --> Mapper[Operation selector and parameter mapper]
Mapper --> Registry[Smartlead tool registry]
Registry --> Tool[Selected Smartlead ToolConfig]
Tool --> API[Smartlead API]
API --> Normalize[Response and error normalization]
Normalize --> Outputs[Workflow outputs]
Reviews (6): Last reviewed commit: "fix(smartlead): remove the dead campaign..." | Re-trigger Greptile
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9aa6963. Configure here.
…um values in docs
A next sequence scheduled to send immediately reported no delay at all:
`Number(next.delayInDays) || null` mapped a legitimate 0 to null.
Tool descriptions built enum lists with template literals. The runtime value
and the LLM-facing tool metadata were correct, but the docs generator reads the
description statically, so the public page rendered
`${SMARTLEAD_CAMPAIGN_STATUSES.join(...)}` instead of START, PAUSED, STOPPED.
The five affected descriptions now spell the values out.
|
@cursor review |
…-integration # Conflicts: # apps/sim/tools/generated/tool-metadata.ts
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d1c543d. Configure here.
…tials
Connecting a real mailbox to the verification account made the email-account
response shapes observable for the first time, and they carry the stored
credentials: `GET /email-accounts/{id}/` and the campaign route return
`password` in plaintext, the list route returns it base64-encoded, and both
carry `imap_password`.
Both tools passed rows through unmapped, so those values would have reached
workflow output, execution logs, and model context. They now select fields
explicitly and omit the credentials.
Verified against the live API: the API response contains the password while the
tool output does not, for both tools.
Also fills in the real email-account fields, which were previously an opaque
array — id, sender identity, SMTP/IMAP host and port, verification state and
last error, sending caps, warmup status, and tags.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0548ef6. Configure here.
…wrong campaign Removing the campaign filters from list_lead_activities and list_inbox_replies left their `activityCampaignId` subblock, its params mapping, and its inputs entry behind. Two problems, the second serious: - On those two operations the field promised campaign scoping the API cannot do. Smartlead rejects every candidate key (`campaign_id`, `campaignId`, `campaign_ids`, `email_campaign_id`), so the value was silently discarded and account-wide results were reported as scoped. - Worse, the field is `mode: 'advanced'`, and advanced subblocks serialize without evaluating their condition. A value left over from listing activities therefore fed `campaignId` on all 32 campaign operations through the `params.campaignId || params.activityCampaignId` fallback. Configuring List Lead Activities with campaign 111, then switching the block to Delete Campaign and leaving Campaign ID blank, would have passed required-validation and deleted campaign 111. Both list tools now also say plainly that Smartlead exposes no campaign filter, rather than advertising one in their descriptions. Also: route mark_lead_complete's next-sequence id through the shared numeric coercion, since Smartlead string-encodes numbers inconsistently and its sibling field already arrives as a string; re-bind the two enum constants that lost their last consumer so the literal descriptions cannot drift undetected; and declare the 17 tool output keys the block was missing — `accounts` most importantly, which is the entire payload of both email-account tools.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d7662d9. Configure here.
Summary
smartlead-errorsextractor, since the API's 400s put the useful text inmessagewhileerroris only"Bad Request"Why not the docs
The docs contradict themselves and the API. The
/statisticspage's prose describes paginated per-email rows while its own example JSON shows a single aggregate object; the live endpoint returns rows. Building against the examples would have produced tools that returnnullfor nearly every output while looking fine in review.Corrected against the live API:
POST /campaigns/new404s — the real path is/campaigns/createGET /campaigns/{id}and/sequencesreturn bare payloads, not{success, data}envelopes{upload_count, total_leads, duplicate_count, …}, not{added_count, skipped_count}GET /leads/{id}is not under/campaigns/and wraps the lead in a single-element array/lead-list/(singular);/crm/leads/tags404s entirelyDELETE .../leads/{id}answers with the bare textsuccess, not JSONEnum values were probed one at a time —
EMAIL_OPENED/EMAIL_CLICKED/EMAIL_REPLIEDare all rejected; the real values areEMAIL_OPEN/EMAIL_LINK_CLICK/EMAIL_REPLY.Quirks encoded in the mappers: string-encoded numbers are normalized so a field never changes type between operations;
seq_delay_detailsreadsdelayInDaysbut writesdelay_in_days; webhook writes echoevent_type_mapobjects while reads returnevent_typesarrays;track_settingsreads back in a vocabulary it will not accept on write.Several lookups answer HTTP 200 with
{}for a missing resource instead of 404, so those tools fail closed rather than reporting an all-null success.Not included, deliberately
add/removeemail accounts to a campaign are the only tools whose success shape is unverified for the same reason; their error path is verifiedType of Change
Testing
All 47 tools executed end-to-end against the live Smartlead API through their real
ToolConfigs. Three rounds of validation agents over the tool layer, block layer, and registry/artifacts; every finding fixed and re-verified live. Test account left empty.Passing: typecheck, lint,
check:api-validation,tool-metadata:check,integration-catalog:check,check:bare-icons, and the error-extractor tests (verified they fail without the fix).Checklist