Skip to content

Commit aae9ce6

Browse files
authored
feat(smartlead): add Smartlead integration (#6352)
* feat(smartlead): add Smartlead integration 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". * feat(smartlead): expand to the core workflow surface and fix review findings 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. * fix(smartlead): correct request params and outputs found in re-validation 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. * fix(smartlead): preserve a zero-day next-sequence delay and render enum 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. * fix(smartlead): stop email-account tools from emitting mailbox credentials 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. * fix(smartlead): remove the dead campaign field that could target the 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.
1 parent 60f6d6d commit aae9ce6

65 files changed

Lines changed: 11513 additions & 4 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/components/icons.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8958,6 +8958,39 @@ export function LogfireIcon(props: SVGProps<SVGSVGElement>) {
89588958
)
89598959
}
89608960

8961+
export function SmartleadIcon(props: SVGProps<SVGSVGElement>) {
8962+
return (
8963+
<svg
8964+
{...props}
8965+
viewBox='0 0 26 26'
8966+
fill='none'
8967+
xmlns='http://www.w3.org/2000/svg'
8968+
aria-hidden='true'
8969+
>
8970+
<g transform='translate(1.4, -1.4)'>
8971+
<path
8972+
d='M12.0577 2.38666L2.39114 12.0532C1.07298 13.3714 1.07298 15.5085 2.39114 16.8267L2.39128 16.8268C3.70944 18.145 5.8466 18.145 7.16477 16.8268L16.8313 7.16029C18.1495 5.84212 18.1495 3.70496 16.8313 2.3868L16.8312 2.38666C15.513 1.0685 13.3758 1.0685 12.0577 2.38666Z'
8973+
fill='#0FDECD'
8974+
/>
8975+
<path
8976+
opacity='0.797'
8977+
d='M16.827 21.6312L7.16043 11.9646C5.84226 10.6465 3.7051 10.6465 2.38694 11.9646L2.3868 11.9648C1.06864 13.2829 1.06864 15.4201 2.3868 16.7383L12.0533 26.4048C13.3715 27.723 15.5087 27.723 16.8268 26.4048L16.827 26.4047C18.1451 25.0865 18.1451 22.9493 16.827 21.6312Z'
8978+
fill='#39BAE9'
8979+
/>
8980+
<path
8981+
d='M18.5001 18.3644L21.6412 15.2233C22.0695 14.795 22.0695 14.1005 21.6412 13.6722C21.2129 13.2439 20.5184 13.2439 20.0901 13.6722L16.949 16.8134C16.5207 17.2417 16.5207 17.9361 16.949 18.3644C17.3773 18.7927 18.0718 18.7927 18.5001 18.3644Z'
8982+
fill='#0CAEE9'
8983+
/>
8984+
<path
8985+
opacity='0.8'
8986+
d='M17.0086 12.1419L20.1498 15.2831C20.5781 15.7114 21.2725 15.7114 21.7008 15.2831L21.701 15.2829C22.1293 14.8546 22.1293 14.1602 21.701 13.7318L18.5598 10.5907C18.1315 10.1624 17.4371 10.1624 17.0088 10.5907L17.0086 10.5908C16.5803 11.0192 16.5803 11.7136 17.0086 12.1419Z'
8987+
fill='#3AE0D2'
8988+
/>
8989+
</g>
8990+
</svg>
8991+
)
8992+
}
8993+
89618994
export function ZohoDeskIcon(props: SVGProps<SVGSVGElement>) {
89628995
return (
89638996
<svg

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ import {
208208
SimTriggerIcon,
209209
SixtyfourIcon,
210210
SlackIcon,
211+
SmartleadIcon,
211212
SmtpIcon,
212213
SportmonksIcon,
213214
SQSIcon,
@@ -491,6 +492,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
491492
similarweb: SimilarwebIcon,
492493
sixtyfour: SixtyfourIcon,
493494
slack: SlackIcon,
495+
smartlead: SmartleadIcon,
494496
smtp: SmtpIcon,
495497
sportmonks: SportmonksIcon,
496498
sqs: SQSIcon,

apps/docs/content/docs/en/integrations/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
"similarweb",
220220
"sixtyfour",
221221
"slack",
222+
"smartlead",
222223
"smtp",
223224
"sportmonks",
224225
"sqs",

apps/docs/content/docs/en/integrations/smartlead.mdx

Lines changed: 5177 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)