Skip to content

Commit 0548ef6

Browse files
committed
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.
1 parent d1c543d commit 0548ef6

5 files changed

Lines changed: 147 additions & 13 deletions

File tree

apps/sim/tools/generated/tool-outputs.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/smartlead/list_campaign_email_accounts.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ErrorExtractorId } from '@/tools/error-extractors'
22
import type {
33
SmartleadCampaignIdParams,
4-
SmartleadOpaqueListResponse,
4+
SmartleadEmailAccountsResponse,
55
} from '@/tools/smartlead/types'
66
import {
7-
opaqueListOutputs,
7+
emailAccountsOutputs,
8+
mapEmailAccount,
89
pathSegment,
910
smartleadArray,
1011
smartleadBaseParamFields,
@@ -16,7 +17,7 @@ import type { ToolConfig } from '@/tools/types'
1617

1718
export const listCampaignEmailAccountsTool: ToolConfig<
1819
SmartleadCampaignIdParams,
19-
SmartleadOpaqueListResponse
20+
SmartleadEmailAccountsResponse
2021
> = {
2122
id: 'smartlead_list_campaign_email_accounts',
2223
name: 'Smartlead List Campaign Email Accounts',
@@ -34,12 +35,13 @@ export const listCampaignEmailAccountsTool: ToolConfig<
3435
headers: smartleadHeaders,
3536
},
3637
transformResponse: async (response) => {
37-
const items = await smartleadArray(response, 'campaign email accounts')
38+
const rows = await smartleadArray(response, 'campaign email accounts')
39+
const accounts = rows.map(mapEmailAccount)
3840

3941
return {
4042
success: true,
41-
output: { items, count: items.length },
43+
output: { accounts, count: accounts.length },
4244
}
4345
},
44-
outputs: opaqueListOutputs,
46+
outputs: emailAccountsOutputs,
4547
}

apps/sim/tools/smartlead/list_email_accounts.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ErrorExtractorId } from '@/tools/error-extractors'
2-
import type { SmartleadBaseParams, SmartleadOpaqueListResponse } from '@/tools/smartlead/types'
2+
import type { SmartleadBaseParams, SmartleadEmailAccountsResponse } from '@/tools/smartlead/types'
33
import {
4-
opaqueListOutputs,
4+
emailAccountsOutputs,
5+
mapEmailAccount,
56
smartleadArray,
67
smartleadBaseParamFields,
78
smartleadHeaders,
@@ -17,7 +18,7 @@ interface ListEmailAccountsParams extends SmartleadBaseParams {
1718

1819
export const listEmailAccountsTool: ToolConfig<
1920
ListEmailAccountsParams,
20-
SmartleadOpaqueListResponse
21+
SmartleadEmailAccountsResponse
2122
> = {
2223
id: 'smartlead_list_email_accounts',
2324
name: 'Smartlead List Email Accounts',
@@ -57,12 +58,13 @@ export const listEmailAccountsTool: ToolConfig<
5758
headers: smartleadHeaders,
5859
},
5960
transformResponse: async (response) => {
60-
const items = await smartleadArray(response, 'email accounts')
61+
const rows = await smartleadArray(response, 'email accounts')
62+
const accounts = rows.map(mapEmailAccount)
6163

6264
return {
6365
success: true,
64-
output: { items, count: items.length },
66+
output: { accounts, count: accounts.length },
6567
}
6668
},
67-
outputs: opaqueListOutputs,
69+
outputs: emailAccountsOutputs,
6870
}

apps/sim/tools/smartlead/types.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,45 @@ export interface SmartleadUpsertWebhookResponse extends ToolResponse {
308308
output: SmartleadSavedWebhook
309309
}
310310

311+
/** Deliberately omits `password` and `imap_password`, which Smartlead returns. */
312+
export interface SmartleadEmailAccount {
313+
id: number | null
314+
from_name: string | null
315+
from_email: string | null
316+
username: string | null
317+
type: string | null
318+
smtp_host: string | null
319+
smtp_port: number | null
320+
smtp_port_type: string | null
321+
imap_host: string | null
322+
imap_port: number | null
323+
imap_port_type: string | null
324+
is_smtp_success: boolean | null
325+
is_imap_success: boolean | null
326+
smtp_failure_error: string | null
327+
imap_failure_error: string | null
328+
message_per_day: number | null
329+
daily_sent_count: number | null
330+
campaign_count: number | null
331+
signature: string | null
332+
custom_tracking_domain: string | null
333+
bcc_email: string | null
334+
different_reply_to_address: string | null
335+
client_id: number | null
336+
is_suspended: boolean | null
337+
warmup_status: string | null
338+
tags: unknown[]
339+
created_at: string | null
340+
updated_at: string | null
341+
}
342+
343+
export interface SmartleadEmailAccountsResponse extends ToolResponse {
344+
output: {
345+
accounts: SmartleadEmailAccount[]
346+
count: number
347+
}
348+
}
349+
311350
export interface SmartleadLeadList {
312351
id: number | null
313352
list_name: string | null
@@ -430,3 +469,4 @@ export type SmartleadResponse =
430469
| SmartleadWebhookSummaryResponse
431470
| SmartleadLeadListsResponse
432471
| SmartleadLeadListResponse
472+
| SmartleadEmailAccountsResponse

apps/sim/tools/smartlead/utils.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
SmartleadCampaignAnalyticsByDate,
77
SmartleadCampaignLead,
88
SmartleadCampaignLeadStats,
9+
SmartleadEmailAccount,
910
SmartleadLead,
1011
SmartleadLeadCampaignData,
1112
SmartleadLeadCategory,
@@ -450,6 +451,50 @@ export function isOk(record: Record<string, unknown>): boolean {
450451
return record.ok === true
451452
}
452453

454+
/**
455+
* Selects the email-account fields explicitly rather than passing the record
456+
* through. Smartlead returns the mailbox credentials on these endpoints —
457+
* `password` in plaintext on the by-id and campaign routes, base64-encoded on the
458+
* list route, plus `imap_password` — and a passthrough would surface them in
459+
* workflow output, logs, and model context.
460+
*/
461+
export function mapEmailAccount(value: unknown): SmartleadEmailAccount {
462+
const record = toRecord(value)
463+
464+
return {
465+
id: toNullableNumber(record.id),
466+
from_name: toStringOrNull(record.from_name),
467+
from_email: toStringOrNull(record.from_email),
468+
username: toStringOrNull(record.username),
469+
type: toStringOrNull(record.type),
470+
smtp_host: toStringOrNull(record.smtp_host),
471+
smtp_port: toNullableNumber(record.smtp_port),
472+
smtp_port_type: toStringOrNull(record.smtp_port_type),
473+
imap_host: toStringOrNull(record.imap_host),
474+
imap_port: toNullableNumber(record.imap_port),
475+
imap_port_type: toStringOrNull(record.imap_port_type),
476+
is_smtp_success: toNullableBoolean(record.is_smtp_success),
477+
is_imap_success: toNullableBoolean(record.is_imap_success),
478+
smtp_failure_error: toStringOrNull(record.smtp_failure_error),
479+
imap_failure_error: toStringOrNull(record.imap_failure_error),
480+
message_per_day: toNullableNumber(record.message_per_day),
481+
daily_sent_count: toNullableNumber(record.daily_sent_count),
482+
campaign_count: toNullableNumber(record.campaign_count),
483+
signature: toStringOrNull(record.signature),
484+
custom_tracking_domain: toStringOrNull(record.custom_tracking_domain),
485+
bcc_email: toStringOrNull(record.bcc_email),
486+
different_reply_to_address: toStringOrNull(record.different_reply_to_address),
487+
client_id: toNullableNumber(record.client_id),
488+
is_suspended: toNullableBoolean(record.is_suspended),
489+
warmup_status: isRecordLike(record.warmup_details)
490+
? toStringOrNull(record.warmup_details.status)
491+
: null,
492+
tags: toArray(record.tags),
493+
created_at: toStringOrNull(record.created_at),
494+
updated_at: toStringOrNull(record.updated_at),
495+
}
496+
}
497+
453498
export function mapLeadList(value: unknown): SmartleadLeadList {
454499
const record = toRecord(value)
455500

@@ -866,6 +911,51 @@ export const listWebhooksOutputs = {
866911

867912
export const upsertWebhookOutputs = webhookProperties satisfies NonNullable<ToolConfig['outputs']>
868913

914+
const emailAccountProperties = {
915+
id: { type: 'number', description: 'Email account ID, used to attach it to a campaign' },
916+
from_name: { type: 'string', description: 'Sender display name', optional: true },
917+
from_email: { type: 'string', description: 'Sender email address' },
918+
username: { type: 'string', description: 'Mailbox username', optional: true },
919+
type: { type: 'string', description: 'Account type (GMAIL, OUTLOOK, SMTP)', optional: true },
920+
smtp_host: { type: 'string', description: 'SMTP host', optional: true },
921+
smtp_port: { type: 'number', description: 'SMTP port', optional: true },
922+
smtp_port_type: { type: 'string', description: 'SMTP encryption type', optional: true },
923+
imap_host: { type: 'string', description: 'IMAP host', optional: true },
924+
imap_port: { type: 'number', description: 'IMAP port', optional: true },
925+
imap_port_type: { type: 'string', description: 'IMAP encryption type', optional: true },
926+
is_smtp_success: { type: 'boolean', description: 'Whether SMTP verification succeeded' },
927+
is_imap_success: { type: 'boolean', description: 'Whether IMAP verification succeeded' },
928+
smtp_failure_error: { type: 'string', description: 'Last SMTP error', optional: true },
929+
imap_failure_error: { type: 'string', description: 'Last IMAP error', optional: true },
930+
message_per_day: { type: 'number', description: 'Daily sending cap', optional: true },
931+
daily_sent_count: { type: 'number', description: 'Messages sent today', optional: true },
932+
campaign_count: { type: 'number', description: 'Campaigns using this account', optional: true },
933+
signature: { type: 'string', description: 'Email signature HTML', optional: true },
934+
custom_tracking_domain: { type: 'string', description: 'Custom tracking domain', optional: true },
935+
bcc_email: { type: 'string', description: 'BCC address', optional: true },
936+
different_reply_to_address: { type: 'string', description: 'Reply-to address', optional: true },
937+
client_id: { type: 'number', description: 'Owning client ID', optional: true },
938+
is_suspended: {
939+
type: 'boolean',
940+
description: 'Whether the account is suspended',
941+
optional: true,
942+
},
943+
warmup_status: { type: 'string', description: 'Warmup status', optional: true },
944+
tags: { type: 'array', description: 'Tags applied to the account' },
945+
created_at: { type: 'string', description: 'Creation timestamp', optional: true },
946+
updated_at: { type: 'string', description: 'Last update timestamp', optional: true },
947+
} satisfies Record<string, OutputProperty>
948+
949+
/** Credentials are intentionally absent — see `mapEmailAccount`. */
950+
export const emailAccountsOutputs = {
951+
accounts: {
952+
type: 'array',
953+
description: 'Email accounts, excluding their stored mailbox credentials',
954+
items: { type: 'object', properties: emailAccountProperties },
955+
},
956+
count: { type: 'number', description: 'Number of accounts returned' },
957+
} satisfies NonNullable<ToolConfig['outputs']>
958+
869959
export const duplicateCampaignOutputs = {
870960
success: { type: 'boolean', description: 'Whether Smartlead duplicated the campaign' },
871961
id: { type: 'number', description: 'ID of the newly created campaign' },

0 commit comments

Comments
 (0)