mirror of
https://github.com/simstudioai/sim.git
synced 2026-08-31 01:11:53 +08:00
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.
This commit is contained in:
@@ -8958,6 +8958,39 @@ export function LogfireIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function SmartleadIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox='0 0 26 26'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<g transform='translate(1.4, -1.4)'>
|
||||
<path
|
||||
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'
|
||||
fill='#0FDECD'
|
||||
/>
|
||||
<path
|
||||
opacity='0.797'
|
||||
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'
|
||||
fill='#39BAE9'
|
||||
/>
|
||||
<path
|
||||
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'
|
||||
fill='#0CAEE9'
|
||||
/>
|
||||
<path
|
||||
opacity='0.8'
|
||||
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'
|
||||
fill='#3AE0D2'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ZohoDeskIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -208,6 +208,7 @@ import {
|
||||
SimTriggerIcon,
|
||||
SixtyfourIcon,
|
||||
SlackIcon,
|
||||
SmartleadIcon,
|
||||
SmtpIcon,
|
||||
SportmonksIcon,
|
||||
SQSIcon,
|
||||
@@ -491,6 +492,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
similarweb: SimilarwebIcon,
|
||||
sixtyfour: SixtyfourIcon,
|
||||
slack: SlackIcon,
|
||||
smartlead: SmartleadIcon,
|
||||
smtp: SmtpIcon,
|
||||
sportmonks: SportmonksIcon,
|
||||
sqs: SQSIcon,
|
||||
|
||||
@@ -219,6 +219,7 @@
|
||||
"similarweb",
|
||||
"sixtyfour",
|
||||
"slack",
|
||||
"smartlead",
|
||||
"smtp",
|
||||
"sportmonks",
|
||||
"sqs",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -281,6 +281,7 @@ import { SimWorkspaceEventBlock } from '@/blocks/blocks/sim_workspace_event'
|
||||
import { SimilarwebBlock, SimilarwebBlockMeta } from '@/blocks/blocks/similarweb'
|
||||
import { SixtyfourBlock, SixtyfourBlockMeta } from '@/blocks/blocks/sixtyfour'
|
||||
import { SlackBlock, SlackBlockMeta, SlackV2Block } from '@/blocks/blocks/slack'
|
||||
import { SmartleadBlock, SmartleadBlockMeta } from '@/blocks/blocks/smartlead'
|
||||
import { SmtpBlock, SmtpBlockMeta } from '@/blocks/blocks/smtp'
|
||||
import { SportmonksBlock, SportmonksBlockMeta } from '@/blocks/blocks/sportmonks'
|
||||
import { SpotifyBlock, SpotifyBlockMeta } from '@/blocks/blocks/spotify'
|
||||
@@ -602,6 +603,7 @@ export const BLOCK_REGISTRY: Record<string, BlockConfig> = {
|
||||
sixtyfour: SixtyfourBlock,
|
||||
slack: SlackBlock,
|
||||
slack_v2: SlackV2Block,
|
||||
smartlead: SmartleadBlock,
|
||||
smtp: SmtpBlock,
|
||||
sportmonks: SportmonksBlock,
|
||||
spotify: SpotifyBlock,
|
||||
@@ -888,6 +890,7 @@ export const BLOCK_META_REGISTRY: Record<string, BlockMeta> = {
|
||||
similarweb: SimilarwebBlockMeta,
|
||||
sixtyfour: SixtyfourBlockMeta,
|
||||
slack: SlackBlockMeta,
|
||||
smartlead: SmartleadBlockMeta,
|
||||
smtp: SmtpBlockMeta,
|
||||
sportmonks: SportmonksBlockMeta,
|
||||
spotify: SpotifyBlockMeta,
|
||||
|
||||
@@ -8958,6 +8958,39 @@ export function LogfireIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function SmartleadIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox='0 0 26 26'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
aria-hidden='true'
|
||||
>
|
||||
<g transform='translate(1.4, -1.4)'>
|
||||
<path
|
||||
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'
|
||||
fill='#0FDECD'
|
||||
/>
|
||||
<path
|
||||
opacity='0.797'
|
||||
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'
|
||||
fill='#39BAE9'
|
||||
/>
|
||||
<path
|
||||
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'
|
||||
fill='#0CAEE9'
|
||||
/>
|
||||
<path
|
||||
opacity='0.8'
|
||||
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'
|
||||
fill='#3AE0D2'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ZohoDeskIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -206,6 +206,7 @@ import {
|
||||
SimTriggerIcon,
|
||||
SixtyfourIcon,
|
||||
SlackIcon,
|
||||
SmartleadIcon,
|
||||
SmtpIcon,
|
||||
SportmonksIcon,
|
||||
SQSIcon,
|
||||
@@ -471,6 +472,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
similarweb: SimilarwebIcon,
|
||||
sixtyfour: SixtyfourIcon,
|
||||
slack: SlackIcon,
|
||||
smartlead: SmartleadIcon,
|
||||
smtp: SmtpIcon,
|
||||
sportmonks: SportmonksIcon,
|
||||
sqs: SQSIcon,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"updatedAt": "2026-08-06",
|
||||
"updatedAt": "2026-08-07",
|
||||
"integrations": [
|
||||
{
|
||||
"type": "onepassword",
|
||||
@@ -17719,6 +17719,213 @@
|
||||
"aiDisclaimer": "Sim agents use AI models to generate messages and responses sent to Slack. AI-generated content can be inaccurate or incomplete, so review automated outputs before relying on them, especially for important communications."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "smartlead",
|
||||
"slug": "smartlead",
|
||||
"name": "Smartlead",
|
||||
"description": "Manage Smartlead cold email campaigns, sequences, and leads",
|
||||
"longDescription": "Integrate Smartlead into workflows. Create campaigns, write multi-step email sequences, import and categorize leads, pull campaign analytics, and register webhooks for engagement events.",
|
||||
"bgColor": "#000000",
|
||||
"iconName": "SmartleadIcon",
|
||||
"docsUrl": "https://docs.sim.ai/integrations/smartlead",
|
||||
"operations": [
|
||||
{
|
||||
"name": "List Campaigns",
|
||||
"description": "Retrieves all Smartlead campaigns for the authenticated account."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign",
|
||||
"description": "Retrieves a single Smartlead campaign by ID."
|
||||
},
|
||||
{
|
||||
"name": "Create Campaign",
|
||||
"description": "Creates a Smartlead campaign. The campaign starts in DRAFTED status; add sequences, email accounts, and a schedule before starting it."
|
||||
},
|
||||
{
|
||||
"name": "Update Campaign Status",
|
||||
"description": "Starts, pauses, or stops a Smartlead campaign. START requires the campaign to already have a schedule, sequences, and at least one email account."
|
||||
},
|
||||
{
|
||||
"name": "Update Campaign Schedule",
|
||||
"description": "Sets the sending window, timezone, and throughput limits for a Smartlead campaign. A campaign cannot be started until a schedule exists."
|
||||
},
|
||||
{
|
||||
"name": "Update Campaign Settings",
|
||||
"description": "Updates tracking, stop-on-activity, and sending settings for a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign Analytics",
|
||||
"description": "Retrieves lifetime performance totals for a Smartlead campaign — sends, opens, clicks, replies, bounces, and lead counts by state."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign Analytics by Date",
|
||||
"description": "Retrieves Smartlead campaign performance totals for a date range. Smartlead rejects ranges longer than roughly one month."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign Sequences",
|
||||
"description": "Retrieves the email sequence steps for a Smartlead campaign, including subjects, bodies, and per-step delays."
|
||||
},
|
||||
{
|
||||
"name": "Save Campaign Sequences",
|
||||
"description": "Replaces the email sequence for a Smartlead campaign. Send every step in one call — steps omitted from the request are removed. An empty subject makes a step reply in the previous thread."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign Statistics",
|
||||
"description": "Retrieves per-email statistics rows for a Smartlead campaign, filterable by sequence step, engagement status, and sent-time range."
|
||||
},
|
||||
{
|
||||
"name": "Add Leads to Campaign",
|
||||
"description": "Adds leads to a Smartlead campaign, up to 400 per call. Returns per-reason counts for leads that were skipped as duplicates, blocked, bounced, or invalid."
|
||||
},
|
||||
{
|
||||
"name": "List Campaign Leads",
|
||||
"description": "Retrieves the leads in a Smartlead campaign with their per-campaign status."
|
||||
},
|
||||
{
|
||||
"name": "Get Lead by Email",
|
||||
"description": "Looks up a Smartlead lead by email address and returns the lead record plus every campaign it belongs to."
|
||||
},
|
||||
{
|
||||
"name": "Update Lead",
|
||||
"description": "Updates a lead in a Smartlead campaign. Smartlead requires the email field even when it is unchanged, and lead edits apply across every campaign the lead belongs to."
|
||||
},
|
||||
{
|
||||
"name": "Update Lead Category",
|
||||
"description": "Sets the category of a lead in a Smartlead campaign, such as Interested or Not Interested. Use List Lead Categories to resolve a category ID."
|
||||
},
|
||||
{
|
||||
"name": "Pause Lead",
|
||||
"description": "Pauses a lead in a Smartlead campaign so it stops receiving sequence emails."
|
||||
},
|
||||
{
|
||||
"name": "Resume Lead",
|
||||
"description": "Resumes a paused lead in a Smartlead campaign, optionally after a delay."
|
||||
},
|
||||
{
|
||||
"name": "List Lead Categories",
|
||||
"description": "Retrieves the lead categories configured on the Smartlead account, with the category IDs needed to categorize a lead."
|
||||
},
|
||||
{
|
||||
"name": "Get Lead Message History",
|
||||
"description": "Retrieves the sent-and-received message history for a lead in a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "List Campaign Webhooks",
|
||||
"description": "Retrieves the webhooks registered on a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "Create or Update Webhook",
|
||||
"description": "Creates a webhook on a Smartlead campaign, or updates an existing one when a webhook ID is supplied. Smartlead requires at least one lead category."
|
||||
},
|
||||
{
|
||||
"name": "Delete Campaign Webhook",
|
||||
"description": "Deletes a webhook from a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "Get Webhook Summary",
|
||||
"description": "Retrieves webhook delivery counts for a Smartlead campaign over a time window, for checking whether events are reaching their destination."
|
||||
},
|
||||
{
|
||||
"name": "Duplicate Campaign",
|
||||
"description": "Copies a Smartlead campaign, including its sequences and settings. The copy is named \"<original> - copy\" and starts in DRAFTED status."
|
||||
},
|
||||
{
|
||||
"name": "Delete Campaign",
|
||||
"description": "Permanently deletes a Smartlead campaign along with its sequences, leads, and webhooks. This cannot be undone."
|
||||
},
|
||||
{
|
||||
"name": "Export Campaign Leads (CSV)",
|
||||
"description": "Exports every lead in a Smartlead campaign as CSV, including engagement counts and the sequence step last sent to each lead."
|
||||
},
|
||||
{
|
||||
"name": "List Campaign Email Accounts",
|
||||
"description": "Retrieves the sending email accounts attached to a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "Add Email Accounts to Campaign",
|
||||
"description": "Attaches sending email accounts to a Smartlead campaign. A campaign needs at least one attached account before it can start."
|
||||
},
|
||||
{
|
||||
"name": "Remove Email Accounts from Campaign",
|
||||
"description": "Detaches sending email accounts from a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "List Email Accounts",
|
||||
"description": "Retrieves the sending email accounts on the Smartlead account, including their IDs for attaching to a campaign."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign Lead Statistics",
|
||||
"description": "Retrieves per-lead engagement statistics for a Smartlead campaign."
|
||||
},
|
||||
{
|
||||
"name": "Get Campaign Mailbox Statistics",
|
||||
"description": "Retrieves per-mailbox sending statistics for a Smartlead campaign, for spotting which sending accounts are underperforming."
|
||||
},
|
||||
{
|
||||
"name": "Get Top-Level Analytics by Date",
|
||||
"description": "Retrieves top-level Smartlead campaign counts for a date range, including positive replies, skipped, failed, and stopped counts not present in the standard analytics."
|
||||
},
|
||||
{
|
||||
"name": "List Lead Activities",
|
||||
"description": "Retrieves recent lead activity across all Smartlead campaigns — opens, clicks, replies, and status changes. Smartlead exposes no campaign filter on this endpoint."
|
||||
},
|
||||
{
|
||||
"name": "Get Lead by ID",
|
||||
"description": "Looks up a Smartlead lead by its ID."
|
||||
},
|
||||
{
|
||||
"name": "Unsubscribe Lead from Campaign",
|
||||
"description": "Unsubscribes a lead from a single Smartlead campaign, leaving it active in other campaigns."
|
||||
},
|
||||
{
|
||||
"name": "Unsubscribe Lead Globally",
|
||||
"description": "Unsubscribes a lead across every Smartlead campaign and adds it to the account-wide unsubscribe list."
|
||||
},
|
||||
{
|
||||
"name": "Mark Lead Complete",
|
||||
"description": "Marks a lead as completed in a Smartlead campaign so it stops receiving further sequence steps."
|
||||
},
|
||||
{
|
||||
"name": "Delete Lead from Campaign",
|
||||
"description": "Removes a lead from a Smartlead campaign. The lead record itself remains on the account."
|
||||
},
|
||||
{
|
||||
"name": "List Inbox Replies",
|
||||
"description": "Retrieves replies from the Smartlead master inbox across all campaigns, optionally limited to unread replies. Smartlead exposes no campaign filter on this endpoint."
|
||||
},
|
||||
{
|
||||
"name": "List Lead Lists",
|
||||
"description": "Retrieves the lead lists on the Smartlead account with their lead counts."
|
||||
},
|
||||
{
|
||||
"name": "Get Lead List",
|
||||
"description": "Retrieves a single Smartlead lead list by ID."
|
||||
},
|
||||
{
|
||||
"name": "Create Lead List",
|
||||
"description": "Creates a lead list on the Smartlead account."
|
||||
},
|
||||
{
|
||||
"name": "Update Lead List",
|
||||
"description": "Renames a Smartlead lead list."
|
||||
},
|
||||
{
|
||||
"name": "Delete Lead List",
|
||||
"description": "Deletes a Smartlead lead list."
|
||||
},
|
||||
{
|
||||
"name": "List Clients",
|
||||
"description": "Retrieves the clients on a Smartlead agency account, including the client IDs used to scope campaigns and email accounts."
|
||||
}
|
||||
],
|
||||
"operationCount": 47,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "api-key",
|
||||
"category": "tools",
|
||||
"integrationType": "email",
|
||||
"tags": ["sales-engagement", "email-marketing", "automation"]
|
||||
},
|
||||
{
|
||||
"type": "smtp",
|
||||
"slug": "smtp",
|
||||
|
||||
@@ -235,4 +235,55 @@ describe('Error Extractors', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('smartlead-errors', () => {
|
||||
it('should extract the domain error string', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: { error: 'Invalid value for status. Allowed values are - START,STOPPED,PAUSED' },
|
||||
}
|
||||
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
||||
'Invalid value for status. Allowed values are - START,STOPPED,PAUSED'
|
||||
)
|
||||
})
|
||||
|
||||
it('should prefer message over the generic "Bad Request" on validation payloads', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 400,
|
||||
data: {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: '"email_status" must be one of [null, opened, clicked, replied]',
|
||||
validation: { source: 'query', keys: ['email_status'] },
|
||||
},
|
||||
}
|
||||
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
||||
'"email_status" must be one of [null, opened, clicked, replied]'
|
||||
)
|
||||
})
|
||||
|
||||
it('should extract the auth failure message', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 401,
|
||||
data: { message: 'Invalid API Key' },
|
||||
}
|
||||
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
||||
'Invalid API Key'
|
||||
)
|
||||
})
|
||||
|
||||
it('should fall back to the status message when no error fields are present', () => {
|
||||
const errorInfo: ErrorInfo = {
|
||||
status: 500,
|
||||
data: {},
|
||||
}
|
||||
|
||||
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
||||
'Request failed with status 500'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -243,6 +243,24 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'smartlead-errors',
|
||||
description:
|
||||
'Smartlead error formats: {error} for domain errors, {message} for auth failures, and Joi validation payloads where {error} is only "Bad Request" and {message} carries the detail',
|
||||
examples: ['Smartlead API'],
|
||||
extract: (errorInfo) => {
|
||||
const data = errorInfo?.data
|
||||
if (!data || typeof data !== 'object') return undefined
|
||||
|
||||
const message = typeof data.message === 'string' ? data.message.trim() : ''
|
||||
const error = typeof data.error === 'string' ? data.error.trim() : ''
|
||||
|
||||
// Joi validation: `error` is the generic "Bad Request", `message` names the field.
|
||||
if (message && data.validation) return message
|
||||
if (error) return error
|
||||
return message || undefined
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'posthog-errors',
|
||||
description: 'PostHog API error format with type/code/detail/attr fields',
|
||||
@@ -331,6 +349,7 @@ export const ErrorExtractorId = {
|
||||
SOAP_FAULT: 'soap-fault',
|
||||
OAUTH_ERROR_DESCRIPTION: 'oauth-error-description',
|
||||
NESTED_ERROR_OBJECT: 'nested-error-object',
|
||||
SMARTLEAD_ERRORS: 'smartlead-errors',
|
||||
POSTHOG_ERRORS: 'posthog-errors',
|
||||
PLAIN_TEXT_DATA: 'plain-text-data',
|
||||
HTTP_STATUS_TEXT: 'http-status-text',
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3697,6 +3697,55 @@ import {
|
||||
slackUpdateMessageTool,
|
||||
slackUpdateViewTool,
|
||||
} from '@/tools/slack'
|
||||
import {
|
||||
smartleadAddEmailAccountsToCampaignTool,
|
||||
smartleadAddLeadsToCampaignTool,
|
||||
smartleadCreateCampaignTool,
|
||||
smartleadCreateLeadListTool,
|
||||
smartleadDeleteCampaignTool,
|
||||
smartleadDeleteCampaignWebhookTool,
|
||||
smartleadDeleteLeadFromCampaignTool,
|
||||
smartleadDeleteLeadListTool,
|
||||
smartleadDuplicateCampaignTool,
|
||||
smartleadExportCampaignLeadsTool,
|
||||
smartleadGetCampaignAnalyticsByDateTool,
|
||||
smartleadGetCampaignAnalyticsTool,
|
||||
smartleadGetCampaignLeadStatisticsTool,
|
||||
smartleadGetCampaignMailboxStatisticsTool,
|
||||
smartleadGetCampaignSequencesTool,
|
||||
smartleadGetCampaignStatisticsTool,
|
||||
smartleadGetCampaignTool,
|
||||
smartleadGetCampaignTopLevelAnalyticsByDateTool,
|
||||
smartleadGetCampaignWebhookSummaryTool,
|
||||
smartleadGetLeadByEmailTool,
|
||||
smartleadGetLeadByIdTool,
|
||||
smartleadGetLeadListTool,
|
||||
smartleadGetLeadMessageHistoryTool,
|
||||
smartleadListCampaignEmailAccountsTool,
|
||||
smartleadListCampaignLeadsTool,
|
||||
smartleadListCampaignsTool,
|
||||
smartleadListCampaignWebhooksTool,
|
||||
smartleadListClientsTool,
|
||||
smartleadListEmailAccountsTool,
|
||||
smartleadListInboxRepliesTool,
|
||||
smartleadListLeadActivitiesTool,
|
||||
smartleadListLeadCategoriesTool,
|
||||
smartleadListLeadListsTool,
|
||||
smartleadMarkLeadCompleteTool,
|
||||
smartleadPauseLeadTool,
|
||||
smartleadRemoveEmailAccountsFromCampaignTool,
|
||||
smartleadResumeLeadTool,
|
||||
smartleadSaveCampaignSequencesTool,
|
||||
smartleadUnsubscribeLeadFromCampaignTool,
|
||||
smartleadUnsubscribeLeadGloballyTool,
|
||||
smartleadUpdateCampaignScheduleTool,
|
||||
smartleadUpdateCampaignSettingsTool,
|
||||
smartleadUpdateCampaignStatusTool,
|
||||
smartleadUpdateLeadCategoryTool,
|
||||
smartleadUpdateLeadListTool,
|
||||
smartleadUpdateLeadTool,
|
||||
smartleadUpsertCampaignWebhookTool,
|
||||
} from '@/tools/smartlead'
|
||||
import { smsSendTool } from '@/tools/sms'
|
||||
import { smtpSendMailTool } from '@/tools/smtp'
|
||||
import {
|
||||
@@ -5748,6 +5797,54 @@ export const tools: Record<string, ToolConfig> = {
|
||||
slack_rename_conversation: slackRenameConversationTool,
|
||||
slack_set_conversation_topic: slackSetConversationTopicTool,
|
||||
slack_set_conversation_purpose: slackSetConversationPurposeTool,
|
||||
smartlead_list_campaigns: smartleadListCampaignsTool,
|
||||
smartlead_get_campaign: smartleadGetCampaignTool,
|
||||
smartlead_create_campaign: smartleadCreateCampaignTool,
|
||||
smartlead_update_campaign_status: smartleadUpdateCampaignStatusTool,
|
||||
smartlead_update_campaign_schedule: smartleadUpdateCampaignScheduleTool,
|
||||
smartlead_update_campaign_settings: smartleadUpdateCampaignSettingsTool,
|
||||
smartlead_get_campaign_analytics: smartleadGetCampaignAnalyticsTool,
|
||||
smartlead_get_campaign_analytics_by_date: smartleadGetCampaignAnalyticsByDateTool,
|
||||
smartlead_get_campaign_sequences: smartleadGetCampaignSequencesTool,
|
||||
smartlead_save_campaign_sequences: smartleadSaveCampaignSequencesTool,
|
||||
smartlead_get_campaign_statistics: smartleadGetCampaignStatisticsTool,
|
||||
smartlead_add_leads_to_campaign: smartleadAddLeadsToCampaignTool,
|
||||
smartlead_list_campaign_leads: smartleadListCampaignLeadsTool,
|
||||
smartlead_get_lead_by_email: smartleadGetLeadByEmailTool,
|
||||
smartlead_update_lead: smartleadUpdateLeadTool,
|
||||
smartlead_update_lead_category: smartleadUpdateLeadCategoryTool,
|
||||
smartlead_pause_lead: smartleadPauseLeadTool,
|
||||
smartlead_resume_lead: smartleadResumeLeadTool,
|
||||
smartlead_list_lead_categories: smartleadListLeadCategoriesTool,
|
||||
smartlead_get_lead_message_history: smartleadGetLeadMessageHistoryTool,
|
||||
smartlead_list_campaign_webhooks: smartleadListCampaignWebhooksTool,
|
||||
smartlead_upsert_campaign_webhook: smartleadUpsertCampaignWebhookTool,
|
||||
smartlead_delete_campaign_webhook: smartleadDeleteCampaignWebhookTool,
|
||||
smartlead_get_campaign_webhook_summary: smartleadGetCampaignWebhookSummaryTool,
|
||||
smartlead_duplicate_campaign: smartleadDuplicateCampaignTool,
|
||||
smartlead_delete_campaign: smartleadDeleteCampaignTool,
|
||||
smartlead_export_campaign_leads: smartleadExportCampaignLeadsTool,
|
||||
smartlead_list_campaign_email_accounts: smartleadListCampaignEmailAccountsTool,
|
||||
smartlead_add_email_accounts_to_campaign: smartleadAddEmailAccountsToCampaignTool,
|
||||
smartlead_remove_email_accounts_from_campaign: smartleadRemoveEmailAccountsFromCampaignTool,
|
||||
smartlead_list_email_accounts: smartleadListEmailAccountsTool,
|
||||
smartlead_get_campaign_lead_statistics: smartleadGetCampaignLeadStatisticsTool,
|
||||
smartlead_get_campaign_mailbox_statistics: smartleadGetCampaignMailboxStatisticsTool,
|
||||
smartlead_get_campaign_top_level_analytics_by_date:
|
||||
smartleadGetCampaignTopLevelAnalyticsByDateTool,
|
||||
smartlead_list_lead_activities: smartleadListLeadActivitiesTool,
|
||||
smartlead_get_lead_by_id: smartleadGetLeadByIdTool,
|
||||
smartlead_unsubscribe_lead_from_campaign: smartleadUnsubscribeLeadFromCampaignTool,
|
||||
smartlead_unsubscribe_lead_globally: smartleadUnsubscribeLeadGloballyTool,
|
||||
smartlead_mark_lead_complete: smartleadMarkLeadCompleteTool,
|
||||
smartlead_delete_lead_from_campaign: smartleadDeleteLeadFromCampaignTool,
|
||||
smartlead_list_inbox_replies: smartleadListInboxRepliesTool,
|
||||
smartlead_list_lead_lists: smartleadListLeadListsTool,
|
||||
smartlead_get_lead_list: smartleadGetLeadListTool,
|
||||
smartlead_create_lead_list: smartleadCreateLeadListTool,
|
||||
smartlead_update_lead_list: smartleadUpdateLeadListTool,
|
||||
smartlead_delete_lead_list: smartleadDeleteLeadListTool,
|
||||
smartlead_list_clients: smartleadListClientsTool,
|
||||
github_repo_info: githubRepoInfoTool,
|
||||
github_repo_info_v2: githubRepoInfoV2Tool,
|
||||
github_latest_commit: githubLatestCommitTool,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface AddEmailAccountsParams extends SmartleadCampaignIdParams {
|
||||
emailAccountIds: number[]
|
||||
}
|
||||
|
||||
export const addEmailAccountsToCampaignTool: ToolConfig<
|
||||
AddEmailAccountsParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_add_email_accounts_to_campaign',
|
||||
name: 'Smartlead Add Email Accounts to Campaign',
|
||||
description:
|
||||
'Attaches sending email accounts to a Smartlead campaign. A campaign needs at least one attached account before it can start.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
emailAccountIds: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IDs of the email accounts to attach, from List Email Accounts',
|
||||
items: { type: 'number' },
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/email-accounts`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({
|
||||
email_account_ids: Array.isArray(params.emailAccountIds) ? params.emailAccountIds : [],
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'email account attach')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) || record.success === true },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadAddLeadsResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
addLeadsOutputs,
|
||||
jsonBody,
|
||||
mapLeadImportResult,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const LEAD_FIELDS = [
|
||||
'email',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'phone_number',
|
||||
'company_name',
|
||||
'website',
|
||||
'location',
|
||||
'linkedin_profile',
|
||||
'company_url',
|
||||
'custom_fields',
|
||||
] as const
|
||||
|
||||
interface AddLeadsToCampaignParams extends SmartleadCampaignIdParams {
|
||||
leads: unknown[]
|
||||
ignoreGlobalBlockList?: boolean
|
||||
ignoreUnsubscribeList?: boolean
|
||||
ignoreDuplicateLeadsInOtherCampaign?: boolean
|
||||
ignoreCommunityBounceList?: boolean
|
||||
}
|
||||
|
||||
/** Keeps only fields Smartlead accepts so stray keys can't fail the whole import. */
|
||||
function toLeadPayload(value: unknown): Record<string, unknown> {
|
||||
const record = isRecordLike(value) ? value : {}
|
||||
const payload: Record<string, unknown> = {}
|
||||
|
||||
for (const field of LEAD_FIELDS) {
|
||||
if (record[field] !== undefined && record[field] !== null && record[field] !== '') {
|
||||
payload[field] = record[field]
|
||||
}
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
export const addLeadsToCampaignTool: ToolConfig<
|
||||
AddLeadsToCampaignParams,
|
||||
SmartleadAddLeadsResponse
|
||||
> = {
|
||||
id: 'smartlead_add_leads_to_campaign',
|
||||
name: 'Smartlead Add Leads to Campaign',
|
||||
description:
|
||||
'Adds leads to a Smartlead campaign, up to 400 per call. Returns per-reason counts for leads that were skipped as duplicates, blocked, bounced, or invalid.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
leads: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Leads to add (max 400). Each entry requires email and accepts first_name, last_name, phone_number, company_name, website, location, linkedin_profile, company_url, and custom_fields.',
|
||||
items: { type: 'object' },
|
||||
},
|
||||
ignoreGlobalBlockList: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Add leads even if they are on the global block list',
|
||||
},
|
||||
ignoreUnsubscribeList: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Add leads even if they previously unsubscribed',
|
||||
},
|
||||
ignoreDuplicateLeadsInOtherCampaign: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Add leads even if they already exist in another campaign',
|
||||
},
|
||||
ignoreCommunityBounceList: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Add leads even if they are on the community bounce list',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/leads`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({
|
||||
lead_list: (Array.isArray(params.leads) ? params.leads : []).map(toLeadPayload),
|
||||
settings: jsonBody({
|
||||
ignore_global_block_list: params.ignoreGlobalBlockList,
|
||||
ignore_unsubscribe_list: params.ignoreUnsubscribeList,
|
||||
ignore_duplicate_leads_in_other_campaign: params.ignoreDuplicateLeadsInOtherCampaign,
|
||||
ignore_community_bounce_list: params.ignoreCommunityBounceList,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead import')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapLeadImportResult(record),
|
||||
}
|
||||
},
|
||||
outputs: addLeadsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadCreateCampaignResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
createCampaignOutputs,
|
||||
jsonBody,
|
||||
mapCreatedCampaign,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CreateCampaignParams extends SmartleadBaseParams {
|
||||
name: string
|
||||
clientId?: number
|
||||
}
|
||||
|
||||
export const createCampaignTool: ToolConfig<CreateCampaignParams, SmartleadCreateCampaignResponse> =
|
||||
{
|
||||
id: 'smartlead_create_campaign',
|
||||
name: 'Smartlead Create Campaign',
|
||||
description:
|
||||
'Creates a Smartlead campaign. The campaign starts in DRAFTED status; add sequences, email accounts, and a schedule before starting it.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Campaign name',
|
||||
},
|
||||
clientId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Client to own the campaign (agency accounts)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl('/campaigns/create', params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
name: params.name,
|
||||
client_id: params.clientId,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'campaign')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapCreatedCampaign(record),
|
||||
}
|
||||
},
|
||||
outputs: createCampaignOutputs,
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadListResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadListOutputs,
|
||||
mapLeadList,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CreateLeadListParams extends SmartleadBaseParams {
|
||||
listName: string
|
||||
}
|
||||
|
||||
export const createLeadListTool: ToolConfig<CreateLeadListParams, SmartleadLeadListResponse> = {
|
||||
id: 'smartlead_create_lead_list',
|
||||
name: 'Smartlead Create Lead List',
|
||||
description: 'Creates a lead list on the Smartlead account.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
listName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name for the new lead list',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl('/lead-list/', params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({ listName: params.listName }),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead list')
|
||||
const data = isRecordLike(record.data) ? record.data : {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapLeadList(data),
|
||||
}
|
||||
},
|
||||
outputs: leadListOutputs,
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteCampaignTool: ToolConfig<SmartleadCampaignIdParams, SmartleadActionResponse> = {
|
||||
id: 'smartlead_delete_campaign',
|
||||
name: 'Smartlead Delete Campaign',
|
||||
description:
|
||||
'Permanently deletes a Smartlead campaign along with its sequences, leads, and webhooks. This cannot be undone.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}`, params.apiKey),
|
||||
method: 'DELETE',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'campaign delete')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface DeleteCampaignWebhookParams extends SmartleadCampaignIdParams {
|
||||
webhookId: number
|
||||
}
|
||||
|
||||
export const deleteCampaignWebhookTool: ToolConfig<
|
||||
DeleteCampaignWebhookParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_delete_campaign_webhook',
|
||||
name: 'Smartlead Delete Campaign Webhook',
|
||||
description: 'Deletes a webhook from a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
webhookId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the webhook to delete, from List Campaign Webhooks',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
// Smartlead takes the webhook id in the body here, not the path.
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/webhooks`, params.apiKey),
|
||||
method: 'DELETE',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({ id: params.webhookId }),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'webhook delete')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignLeadParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteLeadFromCampaignTool: ToolConfig<
|
||||
SmartleadCampaignLeadParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_delete_lead_from_campaign',
|
||||
name: 'Smartlead Delete Lead from Campaign',
|
||||
description:
|
||||
'Removes a lead from a Smartlead campaign. The lead record itself remains on the account.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'DELETE',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
// This endpoint answers with the bare string `success`, not JSON.
|
||||
const body = (await response.text()).trim()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: body === 'success' || body === '"success"' },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadBaseParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface DeleteLeadListParams extends SmartleadBaseParams {
|
||||
leadListId: number
|
||||
}
|
||||
|
||||
export const deleteLeadListTool: ToolConfig<DeleteLeadListParams, SmartleadActionResponse> = {
|
||||
id: 'smartlead_delete_lead_list',
|
||||
name: 'Smartlead Delete Lead List',
|
||||
description: 'Deletes a Smartlead lead list.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
leadListId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead list ID to delete',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl(`/lead-list/${pathSegment(params.leadListId)}`, params.apiKey),
|
||||
method: 'DELETE',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead list delete')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadDuplicateCampaignResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
duplicateCampaignOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const duplicateCampaignTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadDuplicateCampaignResponse
|
||||
> = {
|
||||
id: 'smartlead_duplicate_campaign',
|
||||
name: 'Smartlead Duplicate Campaign',
|
||||
description:
|
||||
'Copies a Smartlead campaign, including its sequences and settings. The copy is named "<original> - copy" and starts in DRAFTED status.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/duplicate`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: () => ({}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'campaign duplicate')
|
||||
const id = record.newCampaignId
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: isOk(record),
|
||||
id: typeof id === 'number' ? id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: duplicateCampaignOutputs,
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadExportLeadsResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
exportLeadsOutputs,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Counts data rows excluding the header, treating newlines inside quoted fields
|
||||
* as part of the value — lead names, locations, and custom fields can contain them.
|
||||
*/
|
||||
function countCsvDataRows(csv: string): number {
|
||||
let rows = 0
|
||||
let inQuotes = false
|
||||
let lineHasContent = false
|
||||
|
||||
for (let i = 0; i < csv.length; i++) {
|
||||
const char = csv[i]
|
||||
if (char === '"') {
|
||||
// A doubled quote inside a quoted field is an escaped literal quote.
|
||||
if (inQuotes && csv[i + 1] === '"') {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inQuotes = !inQuotes
|
||||
lineHasContent = true
|
||||
continue
|
||||
}
|
||||
if (char === '\n' && !inQuotes) {
|
||||
if (lineHasContent) rows++
|
||||
lineHasContent = false
|
||||
continue
|
||||
}
|
||||
if (char !== '\r') lineHasContent = true
|
||||
}
|
||||
if (lineHasContent) rows++
|
||||
|
||||
return Math.max(0, rows - 1)
|
||||
}
|
||||
|
||||
export const exportCampaignLeadsTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadExportLeadsResponse
|
||||
> = {
|
||||
id: 'smartlead_export_campaign_leads',
|
||||
name: 'Smartlead Export Campaign Leads',
|
||||
description:
|
||||
'Exports every lead in a Smartlead campaign as CSV, including engagement counts and the sequence step last sent to each lead.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/leads-export`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: () => ({ Accept: 'text/csv' }),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const csv = await response.text()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
csv,
|
||||
row_count: countCsvDataRows(csv),
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: exportLeadsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadCampaignIdParams, SmartleadCampaignResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
campaignOutputs,
|
||||
mapCampaign,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadExistingRecord,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getCampaignTool: ToolConfig<SmartleadCampaignIdParams, SmartleadCampaignResponse> = {
|
||||
id: 'smartlead_get_campaign',
|
||||
name: 'Smartlead Get Campaign',
|
||||
description: 'Retrieves a single Smartlead campaign by ID.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadExistingRecord(response, 'campaign')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapCampaign(record),
|
||||
}
|
||||
},
|
||||
outputs: campaignOutputs,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignAnalyticsResponse,
|
||||
SmartleadCampaignIdParams,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
campaignAnalyticsOutputs,
|
||||
mapCampaignAnalytics,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadExistingRecord,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getCampaignAnalyticsTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadCampaignAnalyticsResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_analytics',
|
||||
name: 'Smartlead Get Campaign Analytics',
|
||||
description:
|
||||
'Retrieves lifetime performance totals for a Smartlead campaign — sends, opens, clicks, replies, bounces, and lead counts by state.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/analytics`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadExistingRecord(response, 'campaign')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapCampaignAnalytics(record),
|
||||
}
|
||||
},
|
||||
outputs: campaignAnalyticsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignAnalyticsByDateResponse,
|
||||
SmartleadCampaignIdParams,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
campaignAnalyticsByDateOutputs,
|
||||
mapCampaignAnalyticsByDate,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetCampaignAnalyticsByDateParams extends SmartleadCampaignIdParams {
|
||||
startDate: string
|
||||
endDate: string
|
||||
}
|
||||
|
||||
export const getCampaignAnalyticsByDateTool: ToolConfig<
|
||||
GetCampaignAnalyticsByDateParams,
|
||||
SmartleadCampaignAnalyticsByDateResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_analytics_by_date',
|
||||
name: 'Smartlead Get Campaign Analytics By Date',
|
||||
description:
|
||||
'Retrieves Smartlead campaign performance totals for a date range. Smartlead rejects ranges longer than roughly one month.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
startDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Range start date in YYYY-MM-DD format',
|
||||
},
|
||||
endDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Range end date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/analytics-by-date`,
|
||||
params.apiKey,
|
||||
{
|
||||
start_date: params.startDate,
|
||||
end_date: params.endDate,
|
||||
}
|
||||
),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'campaign analytics')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapCampaignAnalyticsByDate(record),
|
||||
}
|
||||
},
|
||||
outputs: campaignAnalyticsByDateOutputs,
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadPaginatedRowsResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
opaqueRows,
|
||||
paginatedRowsOutputs,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetCampaignLeadStatisticsParams extends SmartleadCampaignIdParams {
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export const getCampaignLeadStatisticsTool: ToolConfig<
|
||||
GetCampaignLeadStatisticsParams,
|
||||
SmartleadPaginatedRowsResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_lead_statistics',
|
||||
name: 'Smartlead Get Campaign Lead Statistics',
|
||||
description: 'Retrieves per-lead engagement statistics for a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return (default 100)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to skip (default 0)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
// Smartlead takes `offset` here but echoes it back as `skip`.
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/leads-statistics`, params.apiKey, {
|
||||
limit: params.limit,
|
||||
offset: params.offset,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead statistics')
|
||||
const rows = opaqueRows(record.data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
rows,
|
||||
count: rows.length,
|
||||
has_more: typeof record.hasMore === 'boolean' ? record.hasMore : null,
|
||||
offset: typeof record.skip === 'number' ? record.skip : null,
|
||||
limit: typeof record.limit === 'number' ? record.limit : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: paginatedRowsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadOpaqueListResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
opaqueListOutputs,
|
||||
opaqueRows,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getCampaignMailboxStatisticsTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadOpaqueListResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_mailbox_statistics',
|
||||
name: 'Smartlead Get Campaign Mailbox Statistics',
|
||||
description:
|
||||
'Retrieves per-mailbox sending statistics for a Smartlead campaign, for spotting which sending accounts are underperforming.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/mailbox-statistics`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'mailbox statistics')
|
||||
const items = opaqueRows(record.data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { items, count: items.length },
|
||||
}
|
||||
},
|
||||
outputs: opaqueListOutputs,
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadCampaignIdParams, SmartleadSequencesResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
mapSequence,
|
||||
pathSegment,
|
||||
sequenceOutputs,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getCampaignSequencesTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadSequencesResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_sequences',
|
||||
name: 'Smartlead Get Campaign Sequences',
|
||||
description:
|
||||
'Retrieves the email sequence steps for a Smartlead campaign, including subjects, bodies, and per-step delays.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/sequences`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await smartleadArray(response, 'sequences')
|
||||
const sequences = data.map(mapSequence)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sequences,
|
||||
count: sequences.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: sequenceOutputs,
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadCampaignStatisticsResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
campaignStatisticsOutputs,
|
||||
pathSegment,
|
||||
type SMARTLEAD_EMAIL_STATUSES,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetCampaignStatisticsParams extends SmartleadCampaignIdParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
emailSequenceNumber?: number
|
||||
emailStatus?: (typeof SMARTLEAD_EMAIL_STATUSES)[number]
|
||||
sentTimeStartDate?: string
|
||||
sentTimeEndDate?: string
|
||||
}
|
||||
|
||||
export const getCampaignStatisticsTool: ToolConfig<
|
||||
GetCampaignStatisticsParams,
|
||||
SmartleadCampaignStatisticsResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_statistics',
|
||||
name: 'Smartlead Get Campaign Statistics',
|
||||
description:
|
||||
'Retrieves per-email statistics rows for a Smartlead campaign, filterable by sequence step, engagement status, and sent-time range.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination offset (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return (default 100)',
|
||||
},
|
||||
emailSequenceNumber: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return rows for this sequence step',
|
||||
},
|
||||
emailStatus: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Only return rows with this engagement status: opened, clicked, replied, unsubscribed, bounced',
|
||||
},
|
||||
sentTimeStartDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return rows sent on or after this date (YYYY-MM-DD)',
|
||||
},
|
||||
sentTimeEndDate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return rows sent on or before this date (YYYY-MM-DD)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/statistics`, params.apiKey, {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
email_sequence_number: params.emailSequenceNumber,
|
||||
email_status: params.emailStatus,
|
||||
sent_time_start_date: params.sentTimeStartDate,
|
||||
sent_time_end_date: params.sentTimeEndDate,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'campaign statistics')
|
||||
const stats = Array.isArray(record.data) ? record.data : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
stats,
|
||||
total_stats: Number(record.total_stats ?? stats.length) || 0,
|
||||
offset: typeof record.offset === 'number' ? record.offset : null,
|
||||
limit: typeof record.limit === 'number' ? record.limit : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: campaignStatisticsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadTopLevelAnalyticsResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
mapTopLevelAnalytics,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadExistingRecord,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
topLevelAnalyticsOutputs,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetTopLevelAnalyticsParams extends SmartleadCampaignIdParams {
|
||||
startDate: string
|
||||
endDate: string
|
||||
}
|
||||
|
||||
export const getCampaignTopLevelAnalyticsByDateTool: ToolConfig<
|
||||
GetTopLevelAnalyticsParams,
|
||||
SmartleadTopLevelAnalyticsResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_top_level_analytics_by_date',
|
||||
name: 'Smartlead Get Campaign Top-Level Analytics By Date',
|
||||
description:
|
||||
'Retrieves top-level Smartlead campaign counts for a date range, including positive replies, skipped, failed, and stopped counts not present in the standard analytics.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
startDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Range start date in YYYY-MM-DD format',
|
||||
},
|
||||
endDate: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Range end date in YYYY-MM-DD format',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/top-level-analytics-by-date`,
|
||||
params.apiKey,
|
||||
{ start_date: params.startDate, end_date: params.endDate }
|
||||
),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadExistingRecord(response, 'campaign')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapTopLevelAnalytics(record),
|
||||
}
|
||||
},
|
||||
outputs: topLevelAnalyticsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadWebhookSummaryResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
opaqueRows,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
webhookSummaryOutputs,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetWebhookSummaryParams extends SmartleadCampaignIdParams {
|
||||
fromTime: string
|
||||
toTime: string
|
||||
}
|
||||
|
||||
export const getCampaignWebhookSummaryTool: ToolConfig<
|
||||
GetWebhookSummaryParams,
|
||||
SmartleadWebhookSummaryResponse
|
||||
> = {
|
||||
id: 'smartlead_get_campaign_webhook_summary',
|
||||
name: 'Smartlead Get Campaign Webhook Summary',
|
||||
description:
|
||||
'Retrieves webhook delivery counts for a Smartlead campaign over a time window, for checking whether events are reaching their destination.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
fromTime: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Start of the window as an ISO 8601 timestamp',
|
||||
},
|
||||
toTime: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'End of the window as an ISO 8601 timestamp',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/webhooks/summary`, params.apiKey, {
|
||||
fromTime: params.fromTime,
|
||||
toTime: params.toTime,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'webhook summary')
|
||||
const data = isRecordLike(record.data) ? record.data : {}
|
||||
const summary = opaqueRows(data.summary)
|
||||
const timeRange = isRecordLike(data.timeRange) ? data.timeRange : {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
summary,
|
||||
count: summary.length,
|
||||
from: typeof timeRange.from === 'string' ? timeRange.from : null,
|
||||
to: typeof timeRange.to === 'string' ? timeRange.to : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: webhookSummaryOutputs,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadDetailResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadDetailOutputs,
|
||||
mapLeadDetail,
|
||||
smartleadBaseParamFields,
|
||||
smartleadExistingRecord,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetLeadByEmailParams extends SmartleadBaseParams {
|
||||
email: string
|
||||
}
|
||||
|
||||
export const getLeadByEmailTool: ToolConfig<GetLeadByEmailParams, SmartleadLeadDetailResponse> = {
|
||||
id: 'smartlead_get_lead_by_email',
|
||||
name: 'Smartlead Get Lead by Email',
|
||||
description:
|
||||
'Looks up a Smartlead lead by email address and returns the lead record plus every campaign it belongs to.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead email address to look up',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl('/leads/', params.apiKey, { email: params.email }),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadExistingRecord(response, 'lead', ['id', 'email'])
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapLeadDetail(record),
|
||||
}
|
||||
},
|
||||
outputs: leadDetailOutputs,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadByIdResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadRecordOutputs,
|
||||
mapLead,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetLeadByIdParams extends SmartleadBaseParams {
|
||||
leadId: number
|
||||
}
|
||||
|
||||
export const getLeadByIdTool: ToolConfig<GetLeadByIdParams, SmartleadLeadByIdResponse> = {
|
||||
id: 'smartlead_get_lead_by_id',
|
||||
name: 'Smartlead Get Lead by ID',
|
||||
description: 'Looks up a Smartlead lead by its ID.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
leadId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Smartlead lead ID — the nested lead.id from List Campaign Leads, NOT campaign_lead_map_id',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl(`/leads/${pathSegment(params.leadId)}`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead')
|
||||
// This endpoint wraps the lead in a single-element `data` array.
|
||||
const rows = Array.isArray(record.data) ? record.data : []
|
||||
if (rows.length === 0) throw new Error('Smartlead lead not found')
|
||||
const lead = rows[0] as Record<string, unknown>
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...mapLead(lead),
|
||||
created_at: typeof lead.created_at === 'string' ? lead.created_at : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: leadRecordOutputs,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadListResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadListOutputs,
|
||||
mapLeadList,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetLeadListParams extends SmartleadBaseParams {
|
||||
leadListId: number
|
||||
}
|
||||
|
||||
export const getLeadListTool: ToolConfig<GetLeadListParams, SmartleadLeadListResponse> = {
|
||||
id: 'smartlead_get_lead_list',
|
||||
name: 'Smartlead Get Lead List',
|
||||
description: 'Retrieves a single Smartlead lead list by ID.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
leadListId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead list ID',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl(`/lead-list/${pathSegment(params.leadListId)}`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead list')
|
||||
const data = isRecordLike(record.data) ? record.data : null
|
||||
if (!data) throw new Error('Smartlead lead list not found')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapLeadList(data),
|
||||
}
|
||||
},
|
||||
outputs: leadListOutputs,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignLeadParams,
|
||||
SmartleadMessageHistoryResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
messageHistoryOutputs,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getLeadMessageHistoryTool: ToolConfig<
|
||||
SmartleadCampaignLeadParams,
|
||||
SmartleadMessageHistoryResponse
|
||||
> = {
|
||||
id: 'smartlead_get_lead_message_history',
|
||||
name: 'Smartlead Get Lead Message History',
|
||||
description:
|
||||
'Retrieves the sent-and-received message history for a lead in a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}/message-history`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'message history')
|
||||
const history = Array.isArray(record.history) ? record.history : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
history,
|
||||
count: history.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: messageHistoryOutputs,
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export { addEmailAccountsToCampaignTool as smartleadAddEmailAccountsToCampaignTool } from '@/tools/smartlead/add_email_accounts_to_campaign'
|
||||
export { addLeadsToCampaignTool as smartleadAddLeadsToCampaignTool } from '@/tools/smartlead/add_leads_to_campaign'
|
||||
export { createCampaignTool as smartleadCreateCampaignTool } from '@/tools/smartlead/create_campaign'
|
||||
export { createLeadListTool as smartleadCreateLeadListTool } from '@/tools/smartlead/create_lead_list'
|
||||
export { deleteCampaignTool as smartleadDeleteCampaignTool } from '@/tools/smartlead/delete_campaign'
|
||||
export { deleteCampaignWebhookTool as smartleadDeleteCampaignWebhookTool } from '@/tools/smartlead/delete_campaign_webhook'
|
||||
export { deleteLeadFromCampaignTool as smartleadDeleteLeadFromCampaignTool } from '@/tools/smartlead/delete_lead_from_campaign'
|
||||
export { deleteLeadListTool as smartleadDeleteLeadListTool } from '@/tools/smartlead/delete_lead_list'
|
||||
export { duplicateCampaignTool as smartleadDuplicateCampaignTool } from '@/tools/smartlead/duplicate_campaign'
|
||||
export { exportCampaignLeadsTool as smartleadExportCampaignLeadsTool } from '@/tools/smartlead/export_campaign_leads'
|
||||
export { getCampaignTool as smartleadGetCampaignTool } from '@/tools/smartlead/get_campaign'
|
||||
export { getCampaignAnalyticsTool as smartleadGetCampaignAnalyticsTool } from '@/tools/smartlead/get_campaign_analytics'
|
||||
export { getCampaignAnalyticsByDateTool as smartleadGetCampaignAnalyticsByDateTool } from '@/tools/smartlead/get_campaign_analytics_by_date'
|
||||
export { getCampaignLeadStatisticsTool as smartleadGetCampaignLeadStatisticsTool } from '@/tools/smartlead/get_campaign_lead_statistics'
|
||||
export { getCampaignMailboxStatisticsTool as smartleadGetCampaignMailboxStatisticsTool } from '@/tools/smartlead/get_campaign_mailbox_statistics'
|
||||
export { getCampaignSequencesTool as smartleadGetCampaignSequencesTool } from '@/tools/smartlead/get_campaign_sequences'
|
||||
export { getCampaignStatisticsTool as smartleadGetCampaignStatisticsTool } from '@/tools/smartlead/get_campaign_statistics'
|
||||
export { getCampaignTopLevelAnalyticsByDateTool as smartleadGetCampaignTopLevelAnalyticsByDateTool } from '@/tools/smartlead/get_campaign_top_level_analytics_by_date'
|
||||
export { getCampaignWebhookSummaryTool as smartleadGetCampaignWebhookSummaryTool } from '@/tools/smartlead/get_campaign_webhook_summary'
|
||||
export { getLeadByEmailTool as smartleadGetLeadByEmailTool } from '@/tools/smartlead/get_lead_by_email'
|
||||
export { getLeadByIdTool as smartleadGetLeadByIdTool } from '@/tools/smartlead/get_lead_by_id'
|
||||
export { getLeadListTool as smartleadGetLeadListTool } from '@/tools/smartlead/get_lead_list'
|
||||
export { getLeadMessageHistoryTool as smartleadGetLeadMessageHistoryTool } from '@/tools/smartlead/get_lead_message_history'
|
||||
export { listCampaignEmailAccountsTool as smartleadListCampaignEmailAccountsTool } from '@/tools/smartlead/list_campaign_email_accounts'
|
||||
export { listCampaignLeadsTool as smartleadListCampaignLeadsTool } from '@/tools/smartlead/list_campaign_leads'
|
||||
export { listCampaignWebhooksTool as smartleadListCampaignWebhooksTool } from '@/tools/smartlead/list_campaign_webhooks'
|
||||
export { listCampaignsTool as smartleadListCampaignsTool } from '@/tools/smartlead/list_campaigns'
|
||||
export { listClientsTool as smartleadListClientsTool } from '@/tools/smartlead/list_clients'
|
||||
export { listEmailAccountsTool as smartleadListEmailAccountsTool } from '@/tools/smartlead/list_email_accounts'
|
||||
export { listInboxRepliesTool as smartleadListInboxRepliesTool } from '@/tools/smartlead/list_inbox_replies'
|
||||
export { listLeadActivitiesTool as smartleadListLeadActivitiesTool } from '@/tools/smartlead/list_lead_activities'
|
||||
export { listLeadCategoriesTool as smartleadListLeadCategoriesTool } from '@/tools/smartlead/list_lead_categories'
|
||||
export { listLeadListsTool as smartleadListLeadListsTool } from '@/tools/smartlead/list_lead_lists'
|
||||
export { markLeadCompleteTool as smartleadMarkLeadCompleteTool } from '@/tools/smartlead/mark_lead_complete'
|
||||
export { pauseLeadTool as smartleadPauseLeadTool } from '@/tools/smartlead/pause_lead'
|
||||
export { removeEmailAccountsFromCampaignTool as smartleadRemoveEmailAccountsFromCampaignTool } from '@/tools/smartlead/remove_email_accounts_from_campaign'
|
||||
export { resumeLeadTool as smartleadResumeLeadTool } from '@/tools/smartlead/resume_lead'
|
||||
export { saveCampaignSequencesTool as smartleadSaveCampaignSequencesTool } from '@/tools/smartlead/save_campaign_sequences'
|
||||
export * from '@/tools/smartlead/types'
|
||||
export { unsubscribeLeadFromCampaignTool as smartleadUnsubscribeLeadFromCampaignTool } from '@/tools/smartlead/unsubscribe_lead_from_campaign'
|
||||
export { unsubscribeLeadGloballyTool as smartleadUnsubscribeLeadGloballyTool } from '@/tools/smartlead/unsubscribe_lead_globally'
|
||||
export { updateCampaignScheduleTool as smartleadUpdateCampaignScheduleTool } from '@/tools/smartlead/update_campaign_schedule'
|
||||
export { updateCampaignSettingsTool as smartleadUpdateCampaignSettingsTool } from '@/tools/smartlead/update_campaign_settings'
|
||||
export { updateCampaignStatusTool as smartleadUpdateCampaignStatusTool } from '@/tools/smartlead/update_campaign_status'
|
||||
export { updateLeadTool as smartleadUpdateLeadTool } from '@/tools/smartlead/update_lead'
|
||||
export { updateLeadCategoryTool as smartleadUpdateLeadCategoryTool } from '@/tools/smartlead/update_lead_category'
|
||||
export { updateLeadListTool as smartleadUpdateLeadListTool } from '@/tools/smartlead/update_lead_list'
|
||||
export { upsertCampaignWebhookTool as smartleadUpsertCampaignWebhookTool } from '@/tools/smartlead/upsert_campaign_webhook'
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadEmailAccountsResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
emailAccountsOutputs,
|
||||
mapEmailAccount,
|
||||
pathSegment,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listCampaignEmailAccountsTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadEmailAccountsResponse
|
||||
> = {
|
||||
id: 'smartlead_list_campaign_email_accounts',
|
||||
name: 'Smartlead List Campaign Email Accounts',
|
||||
description: 'Retrieves the sending email accounts attached to a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/email-accounts`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const rows = await smartleadArray(response, 'campaign email accounts')
|
||||
const accounts = rows.map(mapEmailAccount)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { accounts, count: accounts.length },
|
||||
}
|
||||
},
|
||||
outputs: emailAccountsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadListCampaignLeadsResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
listCampaignLeadsOutputs,
|
||||
mapCampaignLead,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListCampaignLeadsParams extends SmartleadCampaignIdParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export const listCampaignLeadsTool: ToolConfig<
|
||||
ListCampaignLeadsParams,
|
||||
SmartleadListCampaignLeadsResponse
|
||||
> = {
|
||||
id: 'smartlead_list_campaign_leads',
|
||||
name: 'Smartlead List Campaign Leads',
|
||||
description: 'Retrieves the leads in a Smartlead campaign with their per-campaign status.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination offset (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Leads to return per page (default 100)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/leads`, params.apiKey, {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'campaign leads')
|
||||
const data = Array.isArray(record.data) ? record.data : []
|
||||
const leads = data.map(mapCampaignLead)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
leads,
|
||||
total_leads: Number(record.total_leads ?? leads.length) || 0,
|
||||
offset: typeof record.offset === 'number' ? record.offset : null,
|
||||
limit: typeof record.limit === 'number' ? record.limit : null,
|
||||
count: leads.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: listCampaignLeadsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadListWebhooksResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
listWebhooksOutputs,
|
||||
mapWebhook,
|
||||
pathSegment,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listCampaignWebhooksTool: ToolConfig<
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadListWebhooksResponse
|
||||
> = {
|
||||
id: 'smartlead_list_campaign_webhooks',
|
||||
name: 'Smartlead List Campaign Webhooks',
|
||||
description: 'Retrieves the webhooks registered on a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/webhooks`, params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await smartleadArray(response, 'webhooks')
|
||||
const webhooks = data.map(mapWebhook)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
webhooks,
|
||||
count: webhooks.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: listWebhooksOutputs,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadListCampaignsResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
listCampaignsOutputs,
|
||||
mapCampaign,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListCampaignsParams extends SmartleadBaseParams {
|
||||
clientId?: number
|
||||
includeTags?: boolean
|
||||
}
|
||||
|
||||
export const listCampaignsTool: ToolConfig<ListCampaignsParams, SmartleadListCampaignsResponse> = {
|
||||
id: 'smartlead_list_campaigns',
|
||||
name: 'Smartlead List Campaigns',
|
||||
description: 'Retrieves all Smartlead campaigns for the authenticated account.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
clientId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return campaigns for this client (agency accounts)',
|
||||
},
|
||||
includeTags: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include campaign tags in the response',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl('/campaigns/', params.apiKey, {
|
||||
client_id: params.clientId,
|
||||
include_tags: params.includeTags,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await smartleadArray(response, 'campaigns')
|
||||
const campaigns = data.map(mapCampaign)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
campaigns,
|
||||
count: campaigns.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: listCampaignsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadOpaqueListResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
opaqueListOutputs,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listClientsTool: ToolConfig<SmartleadBaseParams, SmartleadOpaqueListResponse> = {
|
||||
id: 'smartlead_list_clients',
|
||||
name: 'Smartlead List Clients',
|
||||
description:
|
||||
'Retrieves the clients on a Smartlead agency account, including the client IDs used to scope campaigns and email accounts.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl('/client/', params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const items = await smartleadArray(response, 'clients')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { items, count: items.length },
|
||||
}
|
||||
},
|
||||
outputs: opaqueListOutputs,
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadEmailAccountsResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
emailAccountsOutputs,
|
||||
mapEmailAccount,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListEmailAccountsParams extends SmartleadBaseParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
clientId?: number
|
||||
}
|
||||
|
||||
export const listEmailAccountsTool: ToolConfig<
|
||||
ListEmailAccountsParams,
|
||||
SmartleadEmailAccountsResponse
|
||||
> = {
|
||||
id: 'smartlead_list_email_accounts',
|
||||
name: 'Smartlead List Email Accounts',
|
||||
description:
|
||||
'Retrieves the sending email accounts on the Smartlead account, including their IDs for attaching to a campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination offset (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Accounts to return per page (default 100)',
|
||||
},
|
||||
clientId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return accounts for this client (agency accounts)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl('/email-accounts/', params.apiKey, {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
client_id: params.clientId,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const rows = await smartleadArray(response, 'email accounts')
|
||||
const accounts = rows.map(mapEmailAccount)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { accounts, count: accounts.length },
|
||||
}
|
||||
},
|
||||
outputs: emailAccountsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadPaginatedRowsResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
jsonBody,
|
||||
opaqueRows,
|
||||
paginatedRowsOutputs,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Smartlead rejects every campaign-filter key this endpoint might plausibly take
|
||||
* (`campaign_id`, `campaign_ids`, `campaignIds`), so only pagination is offered.
|
||||
*/
|
||||
interface ListInboxRepliesParams extends SmartleadBaseParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
unreadOnly?: boolean
|
||||
}
|
||||
|
||||
export const listInboxRepliesTool: ToolConfig<
|
||||
ListInboxRepliesParams,
|
||||
SmartleadPaginatedRowsResponse
|
||||
> = {
|
||||
id: 'smartlead_list_inbox_replies',
|
||||
name: 'Smartlead List Inbox Replies',
|
||||
description:
|
||||
'Retrieves replies from the Smartlead master inbox across all campaigns, optionally limited to unread replies. Smartlead exposes no campaign filter on this endpoint.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
unreadOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only unread replies',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination offset (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Replies to return per page',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
params.unreadOnly ? '/master-inbox/unread-replies' : '/master-inbox/inbox-replies',
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
offset: params.offset ?? 0,
|
||||
limit: params.limit,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'inbox replies')
|
||||
const rows = opaqueRows(record.data)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
rows,
|
||||
count: rows.length,
|
||||
has_more: null,
|
||||
offset: typeof record.offset === 'number' ? record.offset : null,
|
||||
limit: typeof record.limit === 'number' ? record.limit : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: paginatedRowsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadPaginatedRowsResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
opaqueRows,
|
||||
paginatedRowsOutputs,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Smartlead exposes no working campaign filter on this endpoint — `campaign_id`,
|
||||
* `campaignId`, `campaign_ids` and `email_campaign_id` are all rejected by its
|
||||
* validator, so only pagination is offered.
|
||||
*/
|
||||
interface ListLeadActivitiesParams extends SmartleadBaseParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export const listLeadActivitiesTool: ToolConfig<
|
||||
ListLeadActivitiesParams,
|
||||
SmartleadPaginatedRowsResponse
|
||||
> = {
|
||||
id: 'smartlead_list_lead_activities',
|
||||
name: 'Smartlead List Lead Activities',
|
||||
description:
|
||||
'Retrieves recent lead activity across all Smartlead campaigns — opens, clicks, replies, and status changes. Smartlead exposes no campaign filter on this endpoint.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination offset (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rows to return per page',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl('/campaigns/all-leads-activities', params.apiKey, {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead activities')
|
||||
const rows = opaqueRows(record.data)
|
||||
const pagination = isRecordLike(record.pagination) ? record.pagination : {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
rows,
|
||||
count: rows.length,
|
||||
has_more: typeof pagination.has_more === 'boolean' ? pagination.has_more : null,
|
||||
offset: typeof pagination.offset === 'number' ? pagination.offset : null,
|
||||
limit: typeof pagination.limit === 'number' ? pagination.limit : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: paginatedRowsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadCategoriesResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadCategoriesOutputs,
|
||||
mapLeadCategory,
|
||||
smartleadArray,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listLeadCategoriesTool: ToolConfig<
|
||||
SmartleadBaseParams,
|
||||
SmartleadLeadCategoriesResponse
|
||||
> = {
|
||||
id: 'smartlead_list_lead_categories',
|
||||
name: 'Smartlead List Lead Categories',
|
||||
description:
|
||||
'Retrieves the lead categories configured on the Smartlead account, with the category IDs needed to categorize a lead.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl('/leads/fetch-categories', params.apiKey),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await smartleadArray(response, 'lead categories')
|
||||
const categories = data.map(mapLeadCategory)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
categories,
|
||||
count: categories.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: leadCategoriesOutputs,
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadListsResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadListsOutputs,
|
||||
mapLeadList,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListLeadListsParams extends SmartleadBaseParams {
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export const listLeadListsTool: ToolConfig<ListLeadListsParams, SmartleadLeadListsResponse> = {
|
||||
id: 'smartlead_list_lead_lists',
|
||||
name: 'Smartlead List Lead Lists',
|
||||
description: 'Retrieves the lead lists on the Smartlead account with their lead counts.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pagination offset (default 0)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lists to return per page',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl('/lead-list/', params.apiKey, {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
}),
|
||||
method: 'GET',
|
||||
headers: smartleadHeaders,
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead lists')
|
||||
const data = isRecordLike(record.data) ? record.data : {}
|
||||
const rows = Array.isArray(data.lists) ? data.lists : []
|
||||
const lists = rows.map(mapLeadList)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
lists,
|
||||
total_count: typeof data.totalCount === 'number' ? data.totalCount : null,
|
||||
count: lists.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: leadListsOutputs,
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadMarkCompleteResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
isOk,
|
||||
markCompleteOutputs,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
toNullableNumber,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface MarkLeadCompleteParams extends SmartleadCampaignIdParams {
|
||||
campaignLeadMapId: number
|
||||
}
|
||||
|
||||
export const markLeadCompleteTool: ToolConfig<
|
||||
MarkLeadCompleteParams,
|
||||
SmartleadMarkCompleteResponse
|
||||
> = {
|
||||
id: 'smartlead_mark_lead_complete',
|
||||
name: 'Smartlead Mark Lead Complete',
|
||||
description:
|
||||
'Marks a lead as completed in a Smartlead campaign so it stops receiving further sequence steps.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
campaignLeadMapId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The campaign_lead_map_id from List Campaign Leads — this endpoint takes the map ID, NOT lead.id',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.campaignLeadMapId)}/manual-complete`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: () => ({}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead completion')
|
||||
const status = isRecordLike(record.status) ? record.status : {}
|
||||
// `nextSequence` is an object ({ id, delayInDays }) when a step remains, else null.
|
||||
// Both members go through the shared numeric coercion: Smartlead string-encodes
|
||||
// numbers inconsistently, and `delayInDays` is legitimately "0" for an immediate step.
|
||||
const next = isRecordLike(status.nextSequence) ? status.nextSequence : null
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: isOk(record),
|
||||
is_last_sequence: typeof status.isLastSequence === 'boolean' ? status.isLastSequence : null,
|
||||
next_sequence_id: next === null ? null : toNullableNumber(next.id),
|
||||
next_sequence_delay_in_days: next === null ? null : toNullableNumber(next.delayInDays),
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: markCompleteOutputs,
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignLeadParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const pauseLeadTool: ToolConfig<SmartleadCampaignLeadParams, SmartleadActionResponse> = {
|
||||
id: 'smartlead_pause_lead',
|
||||
name: 'Smartlead Pause Lead',
|
||||
description: 'Pauses a lead in a Smartlead campaign so it stops receiving sequence emails.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}/pause`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: () => ({}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead pause')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface RemoveEmailAccountsParams extends SmartleadCampaignIdParams {
|
||||
emailAccountIds: number[]
|
||||
}
|
||||
|
||||
export const removeEmailAccountsFromCampaignTool: ToolConfig<
|
||||
RemoveEmailAccountsParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_remove_email_accounts_from_campaign',
|
||||
name: 'Smartlead Remove Email Accounts from Campaign',
|
||||
description: 'Detaches sending email accounts from a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
emailAccountIds: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IDs of the email accounts to detach',
|
||||
items: { type: 'number' },
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/email-accounts`, params.apiKey),
|
||||
method: 'DELETE',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({
|
||||
email_account_ids: Array.isArray(params.emailAccountIds) ? params.emailAccountIds : [],
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'email account detach')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) || record.success === true },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignLeadParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
jsonBody,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ResumeLeadParams extends SmartleadCampaignLeadParams {
|
||||
resumeLeadWithDelayDays?: number
|
||||
}
|
||||
|
||||
export const resumeLeadTool: ToolConfig<ResumeLeadParams, SmartleadActionResponse> = {
|
||||
id: 'smartlead_resume_lead',
|
||||
name: 'Smartlead Resume Lead',
|
||||
description: 'Resumes a paused lead in a Smartlead campaign, optionally after a delay.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
resumeLeadWithDelayDays: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Days to wait before the next email; 0 resumes immediately',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}/resume`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
resume_lead_with_delay_days: params.resumeLeadWithDelayDays,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead resume')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadSaveSequencesResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
isOk,
|
||||
mapSavedSequence,
|
||||
pathSegment,
|
||||
saveSequencesOutputs,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SaveCampaignSequencesParams extends SmartleadCampaignIdParams {
|
||||
sequences: unknown[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Smartlead expects the delay nested as `seq_delay_details.delay_in_days`, but the
|
||||
* value is far easier for a model to supply flat. This accepts either form.
|
||||
*/
|
||||
function toSequencePayload(value: unknown, index: number): Record<string, unknown> {
|
||||
const record = isRecordLike(value) ? value : {}
|
||||
const existingDelay = isRecordLike(record.seq_delay_details) ? record.seq_delay_details : null
|
||||
const delayInDays =
|
||||
existingDelay?.delay_in_days ??
|
||||
existingDelay?.delayInDays ??
|
||||
record.delay_in_days ??
|
||||
record.delayInDays ??
|
||||
1
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
seq_number: record.seq_number ?? index + 1,
|
||||
seq_delay_details: { delay_in_days: Number(delayInDays) || 1 },
|
||||
subject: typeof record.subject === 'string' ? record.subject : '',
|
||||
email_body: typeof record.email_body === 'string' ? record.email_body : '',
|
||||
}
|
||||
|
||||
if (record.id !== undefined) payload.id = record.id
|
||||
if (record.variant_label !== undefined) payload.variant_label = record.variant_label
|
||||
if (record.sequence_variants !== undefined) {
|
||||
payload.sequence_variants = record.sequence_variants
|
||||
}
|
||||
if (record.variant_distribution_type !== undefined) {
|
||||
payload.variant_distribution_type = record.variant_distribution_type
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
export const saveCampaignSequencesTool: ToolConfig<
|
||||
SaveCampaignSequencesParams,
|
||||
SmartleadSaveSequencesResponse
|
||||
> = {
|
||||
id: 'smartlead_save_campaign_sequences',
|
||||
name: 'Smartlead Save Campaign Sequences',
|
||||
description:
|
||||
'Replaces the email sequence for a Smartlead campaign. Send every step in one call — steps omitted from the request are removed. An empty subject makes a step reply in the previous thread.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
sequences: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Ordered sequence steps. Each entry accepts seq_number, delay_in_days, subject, and email_body (HTML). Personalize with {{first_name}} or {{company_name}}.',
|
||||
items: { type: 'object' },
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/sequences`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({
|
||||
sequences: (Array.isArray(params.sequences) ? params.sequences : []).map(toSequencePayload),
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'sequence save')
|
||||
const data = isRecordLike(record.data) ? record.data : {}
|
||||
const saved = Array.isArray(data.sequences) ? data.sequences : []
|
||||
const sequences = saved.map(mapSavedSequence)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: isOk(record),
|
||||
sequences,
|
||||
count: sequences.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: saveSequencesOutputs,
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Smartlead encodes several numeric fields as strings (`total_leads: "1"`,
|
||||
* `sent_count: "0"`, lead ids on some endpoints but not others). Every mapper in
|
||||
* this integration normalizes those to `number` so the same field never changes
|
||||
* type between operations.
|
||||
*/
|
||||
|
||||
export interface SmartleadBaseParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignIdParams extends SmartleadBaseParams {
|
||||
campaignId: number
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignLeadParams extends SmartleadCampaignIdParams {
|
||||
leadId: number
|
||||
}
|
||||
|
||||
export interface SmartleadSchedulerCron {
|
||||
tz: string | null
|
||||
days: number[]
|
||||
startHour: string | null
|
||||
endHour: string | null
|
||||
}
|
||||
|
||||
export interface SmartleadCampaign {
|
||||
id: number | null
|
||||
user_id: number | null
|
||||
name: string | null
|
||||
status: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
track_settings: string[]
|
||||
scheduler_cron_value: SmartleadSchedulerCron | null
|
||||
min_time_btwn_emails: number | null
|
||||
max_leads_per_day: number | null
|
||||
stop_lead_settings: string | null
|
||||
schedule_start_time: string | null
|
||||
enable_ai_esp_matching: boolean | null
|
||||
send_as_plain_text: boolean | null
|
||||
follow_up_percentage: number | null
|
||||
unsubscribe_text: string | null
|
||||
parent_campaign_id: number | null
|
||||
client_id: number | null
|
||||
tags: unknown[]
|
||||
}
|
||||
|
||||
export interface SmartleadSequence {
|
||||
id: number | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
email_campaign_id: number | null
|
||||
seq_number: number | null
|
||||
delay_in_days: number | null
|
||||
subject: string | null
|
||||
email_body: string | null
|
||||
sequence_variants: unknown[]
|
||||
}
|
||||
|
||||
export interface SmartleadSavedSequence {
|
||||
id: number | null
|
||||
seq_number: number | null
|
||||
}
|
||||
|
||||
export interface SmartleadLead {
|
||||
id: number | null
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
email: string | null
|
||||
phone_number: string | null
|
||||
company_name: string | null
|
||||
website: string | null
|
||||
location: string | null
|
||||
linkedin_profile: string | null
|
||||
company_url: string | null
|
||||
custom_fields: Record<string, unknown>
|
||||
is_unsubscribed: boolean | null
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignLead {
|
||||
campaign_lead_map_id: number | null
|
||||
lead_category_id: number | null
|
||||
status: string | null
|
||||
created_at: string | null
|
||||
lead: SmartleadLead
|
||||
}
|
||||
|
||||
export interface SmartleadLeadCampaignData {
|
||||
campaign_id: number | null
|
||||
campaign_name: string | null
|
||||
campaign_lead_map_id: number | null
|
||||
lead_category_id: number | null
|
||||
last_sent_at: string | null
|
||||
last_reply_at: string | null
|
||||
last_activity_at: string | null
|
||||
client_id: number | null
|
||||
client_email: string | null
|
||||
}
|
||||
|
||||
export interface SmartleadLeadDetail extends SmartleadLead {
|
||||
created_at: string | null
|
||||
lead_campaign_data: SmartleadLeadCampaignData[]
|
||||
}
|
||||
|
||||
export interface SmartleadLeadCategory {
|
||||
id: number | null
|
||||
name: string | null
|
||||
sentiment_type: string | null
|
||||
created_at: string | null
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignLeadStats {
|
||||
total: number | null
|
||||
notStarted: number | null
|
||||
inprogress: number | null
|
||||
completed: number | null
|
||||
paused: number | null
|
||||
stopped: number | null
|
||||
blocked: number | null
|
||||
interested: number | null
|
||||
revenue: number | null
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignAnalytics {
|
||||
id: number | null
|
||||
user_id: number | null
|
||||
name: string | null
|
||||
status: string | null
|
||||
created_at: string | null
|
||||
sent_count: number | null
|
||||
unique_sent_count: number | null
|
||||
open_count: number | null
|
||||
unique_open_count: number | null
|
||||
click_count: number | null
|
||||
unique_click_count: number | null
|
||||
reply_count: number | null
|
||||
bounce_count: number | null
|
||||
block_count: number | null
|
||||
unsubscribed_count: number | null
|
||||
total_count: number | null
|
||||
drafted_count: number | null
|
||||
sequence_count: number | null
|
||||
campaign_lead_stats: SmartleadCampaignLeadStats
|
||||
client_id: number | null
|
||||
client_name: string | null
|
||||
client_email: string | null
|
||||
client_company_name: string | null
|
||||
parent_campaign_id: number | null
|
||||
send_as_plain_text: boolean | null
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignAnalyticsByDate {
|
||||
id: number | null
|
||||
user_id: number | null
|
||||
name: string | null
|
||||
status: string | null
|
||||
created_at: string | null
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
sent_count: number | null
|
||||
unique_sent_count: number | null
|
||||
open_count: number | null
|
||||
unique_open_count: number | null
|
||||
click_count: number | null
|
||||
unique_click_count: number | null
|
||||
reply_count: number | null
|
||||
bounce_count: number | null
|
||||
block_count: number | null
|
||||
unsubscribed_count: number | null
|
||||
total_count: number | null
|
||||
drafted_count: number | null
|
||||
}
|
||||
|
||||
export interface SmartleadLeadImportResult {
|
||||
upload_count: number | null
|
||||
total_leads: number | null
|
||||
already_added_to_campaign: number | null
|
||||
duplicate_count: number | null
|
||||
invalid_email_count: number | null
|
||||
block_count: number | null
|
||||
bounce_count: number | null
|
||||
lead_import_stopped_count: number | null
|
||||
is_lead_limit_exhausted: boolean | null
|
||||
invalid_emails: unknown[]
|
||||
unsubscribed_leads: unknown[]
|
||||
}
|
||||
|
||||
export interface SmartleadWebhook {
|
||||
id: number | null
|
||||
name: string | null
|
||||
webhook_url: string | null
|
||||
email_campaign_id: number | null
|
||||
event_types: string[]
|
||||
categories: string[]
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
export interface SmartleadSavedWebhook {
|
||||
id: number | null
|
||||
name: string | null
|
||||
webhook_url: string | null
|
||||
email_campaign_id: number | null
|
||||
event_types: string[]
|
||||
categories: string[]
|
||||
}
|
||||
|
||||
export interface SmartleadListCampaignsResponse extends ToolResponse {
|
||||
output: {
|
||||
campaigns: SmartleadCampaign[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignResponse extends ToolResponse {
|
||||
output: SmartleadCampaign
|
||||
}
|
||||
|
||||
export interface SmartleadCreateCampaignResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number | null
|
||||
name: string | null
|
||||
created_at: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadActionResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignAnalyticsResponse extends ToolResponse {
|
||||
output: SmartleadCampaignAnalytics
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignAnalyticsByDateResponse extends ToolResponse {
|
||||
output: SmartleadCampaignAnalyticsByDate
|
||||
}
|
||||
|
||||
export interface SmartleadSequencesResponse extends ToolResponse {
|
||||
output: {
|
||||
sequences: SmartleadSequence[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadSaveSequencesResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
sequences: SmartleadSavedSequence[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadCampaignStatisticsResponse extends ToolResponse {
|
||||
output: {
|
||||
stats: unknown[]
|
||||
total_stats: number | null
|
||||
offset: number | null
|
||||
limit: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadAddLeadsResponse extends ToolResponse {
|
||||
output: SmartleadLeadImportResult
|
||||
}
|
||||
|
||||
export interface SmartleadListCampaignLeadsResponse extends ToolResponse {
|
||||
output: {
|
||||
leads: SmartleadCampaignLead[]
|
||||
total_leads: number | null
|
||||
offset: number | null
|
||||
limit: number | null
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadLeadDetailResponse extends ToolResponse {
|
||||
output: SmartleadLeadDetail
|
||||
}
|
||||
|
||||
export interface SmartleadLeadCategoriesResponse extends ToolResponse {
|
||||
output: {
|
||||
categories: SmartleadLeadCategory[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadMessageHistoryResponse extends ToolResponse {
|
||||
output: {
|
||||
history: unknown[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadListWebhooksResponse extends ToolResponse {
|
||||
output: {
|
||||
webhooks: SmartleadWebhook[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadUpsertWebhookResponse extends ToolResponse {
|
||||
output: SmartleadSavedWebhook
|
||||
}
|
||||
|
||||
/** Deliberately omits `password` and `imap_password`, which Smartlead returns. */
|
||||
export interface SmartleadEmailAccount {
|
||||
id: number | null
|
||||
from_name: string | null
|
||||
from_email: string | null
|
||||
username: string | null
|
||||
type: string | null
|
||||
smtp_host: string | null
|
||||
smtp_port: number | null
|
||||
smtp_port_type: string | null
|
||||
imap_host: string | null
|
||||
imap_port: number | null
|
||||
imap_port_type: string | null
|
||||
is_smtp_success: boolean | null
|
||||
is_imap_success: boolean | null
|
||||
smtp_failure_error: string | null
|
||||
imap_failure_error: string | null
|
||||
message_per_day: number | null
|
||||
daily_sent_count: number | null
|
||||
campaign_count: number | null
|
||||
signature: string | null
|
||||
custom_tracking_domain: string | null
|
||||
bcc_email: string | null
|
||||
different_reply_to_address: string | null
|
||||
client_id: number | null
|
||||
is_suspended: boolean | null
|
||||
warmup_status: string | null
|
||||
tags: unknown[]
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
export interface SmartleadEmailAccountsResponse extends ToolResponse {
|
||||
output: {
|
||||
accounts: SmartleadEmailAccount[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadLeadList {
|
||||
id: number | null
|
||||
list_name: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
leads_count: number | null
|
||||
active_leads_count: number | null
|
||||
}
|
||||
|
||||
export interface SmartleadDuplicateCampaignResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
id: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadExportLeadsResponse extends ToolResponse {
|
||||
output: {
|
||||
csv: string
|
||||
row_count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadOpaqueListResponse extends ToolResponse {
|
||||
output: {
|
||||
items: unknown[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadPaginatedRowsResponse extends ToolResponse {
|
||||
output: {
|
||||
rows: unknown[]
|
||||
count: number
|
||||
has_more: boolean | null
|
||||
offset: number | null
|
||||
limit: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadTopLevelAnalyticsResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number | null
|
||||
name: string | null
|
||||
status: string | null
|
||||
start_date: string | null
|
||||
end_date: string | null
|
||||
total_count: number | null
|
||||
sent_count: number | null
|
||||
skipped_count: number | null
|
||||
open_count: number | null
|
||||
click_count: number | null
|
||||
reply_count: number | null
|
||||
positive_reply_count: number | null
|
||||
bounce_count: number | null
|
||||
failed_count: number | null
|
||||
stopped_count: number | null
|
||||
unsubscribed_count: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadLeadByIdResponse extends ToolResponse {
|
||||
output: SmartleadLead & { created_at: string | null }
|
||||
}
|
||||
|
||||
export interface SmartleadMarkCompleteResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
is_last_sequence: boolean | null
|
||||
next_sequence_id: number | null
|
||||
next_sequence_delay_in_days: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadWebhookSummaryResponse extends ToolResponse {
|
||||
output: {
|
||||
summary: unknown[]
|
||||
count: number
|
||||
from: string | null
|
||||
to: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadLeadListsResponse extends ToolResponse {
|
||||
output: {
|
||||
lists: SmartleadLeadList[]
|
||||
total_count: number | null
|
||||
count: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface SmartleadLeadListResponse extends ToolResponse {
|
||||
output: SmartleadLeadList
|
||||
}
|
||||
|
||||
export type SmartleadResponse =
|
||||
| SmartleadListCampaignsResponse
|
||||
| SmartleadCampaignResponse
|
||||
| SmartleadCreateCampaignResponse
|
||||
| SmartleadActionResponse
|
||||
| SmartleadCampaignAnalyticsResponse
|
||||
| SmartleadCampaignAnalyticsByDateResponse
|
||||
| SmartleadSequencesResponse
|
||||
| SmartleadSaveSequencesResponse
|
||||
| SmartleadCampaignStatisticsResponse
|
||||
| SmartleadAddLeadsResponse
|
||||
| SmartleadListCampaignLeadsResponse
|
||||
| SmartleadLeadDetailResponse
|
||||
| SmartleadLeadCategoriesResponse
|
||||
| SmartleadMessageHistoryResponse
|
||||
| SmartleadListWebhooksResponse
|
||||
| SmartleadUpsertWebhookResponse
|
||||
| SmartleadDuplicateCampaignResponse
|
||||
| SmartleadExportLeadsResponse
|
||||
| SmartleadOpaqueListResponse
|
||||
| SmartleadPaginatedRowsResponse
|
||||
| SmartleadTopLevelAnalyticsResponse
|
||||
| SmartleadLeadByIdResponse
|
||||
| SmartleadMarkCompleteResponse
|
||||
| SmartleadWebhookSummaryResponse
|
||||
| SmartleadLeadListsResponse
|
||||
| SmartleadLeadListResponse
|
||||
| SmartleadEmailAccountsResponse
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignLeadParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const unsubscribeLeadFromCampaignTool: ToolConfig<
|
||||
SmartleadCampaignLeadParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_unsubscribe_lead_from_campaign',
|
||||
name: 'Smartlead Unsubscribe Lead from Campaign',
|
||||
description:
|
||||
'Unsubscribes a lead from a single Smartlead campaign, leaving it active in other campaigns.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}/unsubscribe`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: () => ({}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead unsubscribe')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadBaseParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UnsubscribeLeadGloballyParams extends SmartleadBaseParams {
|
||||
leadId: number
|
||||
}
|
||||
|
||||
export const unsubscribeLeadGloballyTool: ToolConfig<
|
||||
UnsubscribeLeadGloballyParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_unsubscribe_lead_globally',
|
||||
name: 'Smartlead Unsubscribe Lead Globally',
|
||||
description:
|
||||
'Unsubscribes a lead across every Smartlead campaign and adds it to the account-wide unsubscribe list.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
leadId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Smartlead lead ID — the nested lead.id from List Campaign Leads, NOT campaign_lead_map_id',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/leads/${pathSegment(params.leadId)}/unsubscribe`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: () => ({}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead unsubscribe')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
jsonBody,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateCampaignScheduleParams extends SmartleadCampaignIdParams {
|
||||
timezone: string
|
||||
daysOfTheWeek: number[]
|
||||
startHour: string
|
||||
endHour: string
|
||||
minTimeBetweenEmails?: number
|
||||
maxNewLeadsPerDay?: number
|
||||
scheduleStartTime?: string
|
||||
}
|
||||
|
||||
export const updateCampaignScheduleTool: ToolConfig<
|
||||
UpdateCampaignScheduleParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_update_campaign_schedule',
|
||||
name: 'Smartlead Update Campaign Schedule',
|
||||
description:
|
||||
'Sets the sending window, timezone, and throughput limits for a Smartlead campaign. A campaign cannot be started until a schedule exists.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
timezone: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'IANA timezone for the sending window, e.g. America/Los_Angeles',
|
||||
},
|
||||
daysOfTheWeek: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sending days as ISO weekday numbers, where 1 is Monday and 7 is Sunday',
|
||||
items: { type: 'number' },
|
||||
},
|
||||
startHour: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sending window start in 24-hour HH:MM format, e.g. 09:00',
|
||||
},
|
||||
endHour: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sending window end in 24-hour HH:MM format, e.g. 17:00',
|
||||
},
|
||||
minTimeBetweenEmails: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum minutes to wait between emails',
|
||||
},
|
||||
maxNewLeadsPerDay: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum new leads to contact per day',
|
||||
},
|
||||
scheduleStartTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 8601 timestamp to begin sending, omit to start immediately',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/schedule`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
timezone: params.timezone,
|
||||
days_of_the_week: params.daysOfTheWeek,
|
||||
start_hour: params.startHour,
|
||||
end_hour: params.endHour,
|
||||
min_time_btw_emails: params.minTimeBetweenEmails,
|
||||
max_new_leads_per_day: params.maxNewLeadsPerDay,
|
||||
schedule_start_time: params.scheduleStartTime,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'schedule update')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
jsonBody,
|
||||
pathSegment,
|
||||
type SMARTLEAD_STOP_LEAD_SETTINGS,
|
||||
type SMARTLEAD_TRACK_SETTINGS,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateCampaignSettingsParams extends SmartleadCampaignIdParams {
|
||||
trackSettings?: (typeof SMARTLEAD_TRACK_SETTINGS)[number][]
|
||||
stopLeadSettings?: (typeof SMARTLEAD_STOP_LEAD_SETTINGS)[number]
|
||||
sendAsPlainText?: boolean
|
||||
followUpPercentage?: number
|
||||
unsubscribeText?: string
|
||||
enableAiEspMatching?: boolean
|
||||
}
|
||||
|
||||
export const updateCampaignSettingsTool: ToolConfig<
|
||||
UpdateCampaignSettingsParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_update_campaign_settings',
|
||||
name: 'Smartlead Update Campaign Settings',
|
||||
description: 'Updates tracking, stop-on-activity, and sending settings for a Smartlead campaign.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
trackSettings: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Tracking to disable. Allowed values: DONT_TRACK_EMAIL_OPEN, DONT_TRACK_LINK_CLICK, DONT_TRACK_REPLY_TO_AN_EMAIL',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
stopLeadSettings: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Lead activity that stops the sequence: REPLY_TO_AN_EMAIL, CLICK_ON_A_LINK, OPEN_AN_EMAIL',
|
||||
},
|
||||
sendAsPlainText: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Send campaign emails as plain text',
|
||||
},
|
||||
followUpPercentage: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Percentage of leads that receive follow-ups',
|
||||
},
|
||||
unsubscribeText: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unsubscribe text appended to emails',
|
||||
},
|
||||
enableAiEspMatching: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Match sending accounts to recipient email providers',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/settings`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
track_settings: params.trackSettings,
|
||||
stop_lead_settings: params.stopLeadSettings,
|
||||
send_as_plain_text: params.sendAsPlainText,
|
||||
follow_up_percentage: params.followUpPercentage,
|
||||
unsubscribe_text: params.unsubscribeText,
|
||||
enable_ai_esp_matching: params.enableAiEspMatching,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'settings update')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignIdParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
pathSegment,
|
||||
type SMARTLEAD_CAMPAIGN_STATUSES,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateCampaignStatusParams extends SmartleadCampaignIdParams {
|
||||
status: (typeof SMARTLEAD_CAMPAIGN_STATUSES)[number]
|
||||
}
|
||||
|
||||
export const updateCampaignStatusTool: ToolConfig<
|
||||
UpdateCampaignStatusParams,
|
||||
SmartleadActionResponse
|
||||
> = {
|
||||
id: 'smartlead_update_campaign_status',
|
||||
name: 'Smartlead Update Campaign Status',
|
||||
description:
|
||||
'Starts, pauses, or stops a Smartlead campaign. START requires the campaign to already have a schedule, sequences, and at least one email account.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
status: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Target status: START, PAUSED, STOPPED',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/status`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({ status: params.status }),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'status update')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignLeadParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
jsonBody,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateLeadParams extends SmartleadCampaignLeadParams {
|
||||
email: string
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
phoneNumber?: string
|
||||
companyName?: string
|
||||
website?: string
|
||||
location?: string
|
||||
linkedinProfile?: string
|
||||
companyUrl?: string
|
||||
customFields?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const updateLeadTool: ToolConfig<UpdateLeadParams, SmartleadActionResponse> = {
|
||||
id: 'smartlead_update_lead',
|
||||
name: 'Smartlead Update Lead',
|
||||
description:
|
||||
'Updates a lead in a Smartlead campaign. Smartlead requires the email field even when it is unchanged, and lead edits apply across every campaign the lead belongs to.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead email address — required by Smartlead even when unchanged',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead first name',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead last name',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead phone number',
|
||||
},
|
||||
companyName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead company name',
|
||||
},
|
||||
website: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead website',
|
||||
},
|
||||
location: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead location',
|
||||
},
|
||||
linkedinProfile: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead LinkedIn profile URL',
|
||||
},
|
||||
companyUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead company URL',
|
||||
},
|
||||
customFields: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom field values keyed by field name (max 200 keys)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
email: params.email,
|
||||
first_name: params.firstName,
|
||||
last_name: params.lastName,
|
||||
phone_number: params.phoneNumber,
|
||||
company_name: params.companyName,
|
||||
website: params.website,
|
||||
location: params.location,
|
||||
linkedin_profile: params.linkedinProfile,
|
||||
company_url: params.companyUrl,
|
||||
custom_fields: params.customFields,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead update')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadActionResponse, SmartleadCampaignLeadParams } from '@/tools/smartlead/types'
|
||||
import {
|
||||
actionOutputs,
|
||||
isOk,
|
||||
jsonBody,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadLeadIdParamField,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateLeadCategoryParams extends SmartleadCampaignLeadParams {
|
||||
categoryId: number
|
||||
pauseLead?: boolean
|
||||
}
|
||||
|
||||
export const updateLeadCategoryTool: ToolConfig<UpdateLeadCategoryParams, SmartleadActionResponse> =
|
||||
{
|
||||
id: 'smartlead_update_lead_category',
|
||||
name: 'Smartlead Update Lead Category',
|
||||
description:
|
||||
'Sets the category of a lead in a Smartlead campaign, such as Interested or Not Interested. Use List Lead Categories to resolve a category ID.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
...smartleadLeadIdParamField,
|
||||
categoryId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead category ID to apply',
|
||||
},
|
||||
pauseLead: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Also pause the lead sequence when applying the category',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(
|
||||
`/campaigns/${pathSegment(params.campaignId)}/leads/${pathSegment(params.leadId)}/category`,
|
||||
params.apiKey
|
||||
),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) =>
|
||||
jsonBody({
|
||||
category_id: params.categoryId,
|
||||
pause_lead: params.pauseLead,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead category update')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { success: isOk(record) },
|
||||
}
|
||||
},
|
||||
outputs: actionOutputs,
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { isRecordLike } from '@sim/utils/object'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type { SmartleadBaseParams, SmartleadLeadListResponse } from '@/tools/smartlead/types'
|
||||
import {
|
||||
leadListOutputs,
|
||||
mapLeadList,
|
||||
pathSegment,
|
||||
smartleadBaseParamFields,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateLeadListParams extends SmartleadBaseParams {
|
||||
leadListId: number
|
||||
listName: string
|
||||
}
|
||||
|
||||
export const updateLeadListTool: ToolConfig<UpdateLeadListParams, SmartleadLeadListResponse> = {
|
||||
id: 'smartlead_update_lead_list',
|
||||
name: 'Smartlead Update Lead List',
|
||||
description: 'Renames a Smartlead lead list.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
leadListId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Lead list ID to update',
|
||||
},
|
||||
listName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New name for the lead list',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => smartleadUrl(`/lead-list/${pathSegment(params.leadListId)}`, params.apiKey),
|
||||
method: 'PUT',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({ listName: params.listName }),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'lead list')
|
||||
const data = isRecordLike(record.data) ? record.data : {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapLeadList(data),
|
||||
}
|
||||
},
|
||||
outputs: leadListOutputs,
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
SmartleadCampaignIdParams,
|
||||
SmartleadUpsertWebhookResponse,
|
||||
} from '@/tools/smartlead/types'
|
||||
import {
|
||||
mapSavedWebhook,
|
||||
pathSegment,
|
||||
type SMARTLEAD_WEBHOOK_EVENT_TYPES,
|
||||
smartleadBaseParamFields,
|
||||
smartleadCampaignIdParamField,
|
||||
smartleadHeaders,
|
||||
smartleadRecord,
|
||||
smartleadUrl,
|
||||
upsertWebhookOutputs,
|
||||
} from '@/tools/smartlead/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpsertCampaignWebhookParams extends SmartleadCampaignIdParams {
|
||||
name: string
|
||||
webhookUrl: string
|
||||
eventTypes: (typeof SMARTLEAD_WEBHOOK_EVENT_TYPES)[number][]
|
||||
categories: string[]
|
||||
webhookId?: number
|
||||
}
|
||||
|
||||
export const upsertCampaignWebhookTool: ToolConfig<
|
||||
UpsertCampaignWebhookParams,
|
||||
SmartleadUpsertWebhookResponse
|
||||
> = {
|
||||
id: 'smartlead_upsert_campaign_webhook',
|
||||
name: 'Smartlead Create or Update Campaign Webhook',
|
||||
description:
|
||||
'Creates a webhook on a Smartlead campaign, or updates an existing one when a webhook ID is supplied. Smartlead requires at least one lead category.',
|
||||
version: '1.0.0',
|
||||
errorExtractor: ErrorExtractorId.SMARTLEAD_ERRORS,
|
||||
params: {
|
||||
...smartleadBaseParamFields,
|
||||
...smartleadCampaignIdParamField,
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Webhook name',
|
||||
},
|
||||
webhookUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'HTTPS URL Smartlead should post events to',
|
||||
},
|
||||
eventTypes: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Events to subscribe to. Allowed values: EMAIL_SENT, EMAIL_OPEN, EMAIL_LINK_CLICK, EMAIL_REPLY, EMAIL_BOUNCE, LEAD_UNSUBSCRIBED, LEAD_CATEGORY_UPDATED',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
categories: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Lead category names the webhook applies to, e.g. Interested. Smartlead rejects an empty list.',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
webhookId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Existing webhook ID to update; omit to create a new webhook',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
smartleadUrl(`/campaigns/${pathSegment(params.campaignId)}/webhooks`, params.apiKey),
|
||||
method: 'POST',
|
||||
headers: smartleadHeaders,
|
||||
body: (params) => ({
|
||||
id: params.webhookId ?? null,
|
||||
name: params.name,
|
||||
webhook_url: params.webhookUrl,
|
||||
event_types: Array.isArray(params.eventTypes) ? params.eventTypes : [],
|
||||
categories: Array.isArray(params.categories) ? params.categories : [],
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const record = await smartleadRecord(response, 'webhook')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: mapSavedWebhook(record),
|
||||
}
|
||||
},
|
||||
outputs: upsertWebhookOutputs,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user