d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
373 lines
14 KiB
TypeScript
373 lines
14 KiB
TypeScript
import { filterUndefined, isRecordLike } from '@sim/utils/object'
|
|
import type {
|
|
InstantlyCampaign,
|
|
InstantlyEmail,
|
|
InstantlyLead,
|
|
InstantlyLeadList,
|
|
} from '@/tools/instantly/types'
|
|
import type { ToolConfig } from '@/tools/types'
|
|
|
|
const INSTANTLY_API_BASE_URL = 'https://api.instantly.ai'
|
|
|
|
type InstantlyBaseParams = { apiKey: string }
|
|
type JsonRecord = Record<string, unknown>
|
|
|
|
export const instantlyBaseParamFields = {
|
|
apiKey: {
|
|
type: 'string',
|
|
required: true,
|
|
visibility: 'user-only',
|
|
description: 'Instantly API key with the required V2 scopes',
|
|
},
|
|
} satisfies ToolConfig<InstantlyBaseParams>['params']
|
|
|
|
export const instantlyHeaders = (params: InstantlyBaseParams) => ({
|
|
Authorization: `Bearer ${params.apiKey.trim()}`,
|
|
'Content-Type': 'application/json',
|
|
})
|
|
|
|
export function instantlyUrl(path: string, query?: Record<string, unknown>): string {
|
|
const url = new URL(path, INSTANTLY_API_BASE_URL)
|
|
|
|
if (query) {
|
|
for (const [key, value] of Object.entries(query)) {
|
|
if (value === undefined || value === null || value === '') continue
|
|
url.searchParams.append(key, String(value))
|
|
}
|
|
}
|
|
|
|
return url.toString()
|
|
}
|
|
|
|
export function compactBody(values: Record<string, unknown>): Record<string, unknown> {
|
|
return filterUndefined(values)
|
|
}
|
|
|
|
export async function parseInstantlyResponse(response: Response): Promise<unknown> {
|
|
const data = await parseJsonResponse(response)
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
extractInstantlyError(data, `Instantly API request failed (${response.status})`)
|
|
)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
export function asRecord(value: unknown): JsonRecord {
|
|
return isRecordLike(value) ? value : {}
|
|
}
|
|
|
|
export function getItems(value: unknown): JsonRecord[] {
|
|
const data = asRecord(value)
|
|
return Array.isArray(data.items) ? data.items.map(asRecord) : []
|
|
}
|
|
|
|
export function getNextStartingAfter(value: unknown): string | null {
|
|
const data = asRecord(value)
|
|
return asString(data.next_starting_after)
|
|
}
|
|
|
|
export function mapLead(value: unknown): InstantlyLead {
|
|
const lead = asRecord(value)
|
|
|
|
return {
|
|
id: asString(lead.id),
|
|
timestamp_created: asString(lead.timestamp_created),
|
|
timestamp_updated: asString(lead.timestamp_updated),
|
|
organization: asString(lead.organization),
|
|
campaign: asString(lead.campaign),
|
|
status: asNumber(lead.status),
|
|
email: asString(lead.email),
|
|
personalization: asString(lead.personalization),
|
|
website: asString(lead.website),
|
|
last_name: asString(lead.last_name),
|
|
first_name: asString(lead.first_name),
|
|
company_name: asString(lead.company_name),
|
|
job_title: asString(lead.job_title),
|
|
phone: asString(lead.phone),
|
|
email_open_count: asNumber(lead.email_open_count),
|
|
email_reply_count: asNumber(lead.email_reply_count),
|
|
email_click_count: asNumber(lead.email_click_count),
|
|
company_domain: asString(lead.company_domain),
|
|
payload: isRecordLike(lead.payload) ? lead.payload : null,
|
|
lt_interest_status: asNumber(lead.lt_interest_status),
|
|
}
|
|
}
|
|
|
|
export function mapCampaign(value: unknown): InstantlyCampaign {
|
|
const campaign = asRecord(value)
|
|
|
|
return {
|
|
id: asString(campaign.id),
|
|
name: asString(campaign.name),
|
|
pl_value: asNumber(campaign.pl_value),
|
|
status: asNumber(campaign.status),
|
|
is_evergreen: asBoolean(campaign.is_evergreen),
|
|
timestamp_created: asString(campaign.timestamp_created),
|
|
timestamp_updated: asString(campaign.timestamp_updated),
|
|
email_gap: asNumber(campaign.email_gap),
|
|
daily_limit: asNumber(campaign.daily_limit),
|
|
daily_max_leads: asNumber(campaign.daily_max_leads),
|
|
open_tracking: asBoolean(campaign.open_tracking),
|
|
stop_on_reply: asBoolean(campaign.stop_on_reply),
|
|
sequences: Array.isArray(campaign.sequences) ? campaign.sequences : [],
|
|
campaign_schedule: isRecordLike(campaign.campaign_schedule) ? campaign.campaign_schedule : null,
|
|
}
|
|
}
|
|
|
|
export function mapEmail(value: unknown): InstantlyEmail {
|
|
const email = asRecord(value)
|
|
const body = asRecord(email.body)
|
|
|
|
return {
|
|
id: asString(email.id),
|
|
timestamp_created: asString(email.timestamp_created),
|
|
timestamp_email: asString(email.timestamp_email),
|
|
message_id: asString(email.message_id),
|
|
subject: asString(email.subject),
|
|
from_address_email: asString(email.from_address_email),
|
|
to_address_email_list: asString(email.to_address_email_list),
|
|
cc_address_email_list: asString(email.cc_address_email_list),
|
|
bcc_address_email_list: asString(email.bcc_address_email_list),
|
|
reply_to: asString(email.reply_to),
|
|
body: {
|
|
text: asString(body.text),
|
|
html: asString(body.html),
|
|
},
|
|
organization_id: asString(email.organization_id),
|
|
campaign_id: asString(email.campaign_id),
|
|
subsequence_id: asString(email.subsequence_id),
|
|
list_id: asString(email.list_id),
|
|
lead: asString(email.lead),
|
|
lead_id: asString(email.lead_id),
|
|
eaccount: asString(email.eaccount),
|
|
ue_type: asNumber(email.ue_type),
|
|
is_unread: asNumber(email.is_unread),
|
|
is_auto_reply: asNumber(email.is_auto_reply),
|
|
i_status: asNumber(email.i_status),
|
|
thread_id: asString(email.thread_id),
|
|
content_preview: asString(email.content_preview),
|
|
}
|
|
}
|
|
|
|
export function mapLeadList(value: unknown): InstantlyLeadList {
|
|
const leadList = asRecord(value)
|
|
|
|
return {
|
|
id: asString(leadList.id),
|
|
organization_id: asString(leadList.organization_id),
|
|
has_enrichment_task: asBoolean(leadList.has_enrichment_task),
|
|
owned_by: asString(leadList.owned_by),
|
|
name: asString(leadList.name),
|
|
timestamp_created: asString(leadList.timestamp_created),
|
|
}
|
|
}
|
|
|
|
export const leadOutputs = {
|
|
lead: {
|
|
type: 'object',
|
|
description: 'Lead details',
|
|
properties: {
|
|
id: { type: 'string', description: 'Lead ID', nullable: true },
|
|
email: { type: 'string', description: 'Lead email address', nullable: true },
|
|
first_name: { type: 'string', description: 'Lead first name', nullable: true },
|
|
last_name: { type: 'string', description: 'Lead last name', nullable: true },
|
|
company_name: { type: 'string', description: 'Lead company name', nullable: true },
|
|
job_title: { type: 'string', description: 'Lead job title', nullable: true },
|
|
campaign: { type: 'string', description: 'Campaign ID', nullable: true },
|
|
status: { type: 'number', description: 'Lead status', nullable: true },
|
|
payload: { type: 'json', description: 'Lead custom variables', nullable: true },
|
|
},
|
|
},
|
|
id: { type: 'string', description: 'Lead ID', optional: true },
|
|
email_address: { type: 'string', description: 'Lead email address', optional: true },
|
|
first_name: { type: 'string', description: 'Lead first name', optional: true },
|
|
last_name: { type: 'string', description: 'Lead last name', optional: true },
|
|
campaign: { type: 'string', description: 'Campaign ID', optional: true },
|
|
status: { type: 'number', description: 'Lead status', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const leadsListOutputs = {
|
|
leads: {
|
|
type: 'array',
|
|
description: 'List of leads',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Lead ID', nullable: true },
|
|
email: { type: 'string', description: 'Lead email address', nullable: true },
|
|
first_name: { type: 'string', description: 'Lead first name', nullable: true },
|
|
last_name: { type: 'string', description: 'Lead last name', nullable: true },
|
|
company_name: { type: 'string', description: 'Lead company name', nullable: true },
|
|
campaign: { type: 'string', description: 'Campaign ID', nullable: true },
|
|
status: { type: 'number', description: 'Lead status', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
count: { type: 'number', description: 'Number of leads returned' },
|
|
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const campaignOutputs = {
|
|
campaign: {
|
|
type: 'object',
|
|
description: 'Campaign details',
|
|
properties: {
|
|
id: { type: 'string', description: 'Campaign ID', nullable: true },
|
|
name: { type: 'string', description: 'Campaign name', nullable: true },
|
|
status: { type: 'number', description: 'Campaign status', nullable: true },
|
|
daily_limit: { type: 'number', description: 'Daily sending limit', nullable: true },
|
|
daily_max_leads: { type: 'number', description: 'Daily max new leads', nullable: true },
|
|
open_tracking: {
|
|
type: 'boolean',
|
|
description: 'Whether open tracking is enabled',
|
|
nullable: true,
|
|
},
|
|
},
|
|
},
|
|
id: { type: 'string', description: 'Campaign ID', optional: true },
|
|
name: { type: 'string', description: 'Campaign name', optional: true },
|
|
status: { type: 'number', description: 'Campaign status', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const campaignActionOutputs = {
|
|
...campaignOutputs,
|
|
message: { type: 'string', description: 'Confirmation message from Instantly', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const campaignsListOutputs = {
|
|
campaigns: {
|
|
type: 'array',
|
|
description: 'List of campaigns',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Campaign ID', nullable: true },
|
|
name: { type: 'string', description: 'Campaign name', nullable: true },
|
|
status: { type: 'number', description: 'Campaign status', nullable: true },
|
|
daily_limit: { type: 'number', description: 'Daily sending limit', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
count: { type: 'number', description: 'Number of campaigns returned' },
|
|
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const emailOutputs = {
|
|
email: {
|
|
type: 'object',
|
|
description: 'Email details',
|
|
properties: {
|
|
id: { type: 'string', description: 'Email ID', nullable: true },
|
|
subject: { type: 'string', description: 'Email subject', nullable: true },
|
|
from_address_email: { type: 'string', description: 'Sender email', nullable: true },
|
|
to_address_email_list: {
|
|
type: 'string',
|
|
description: 'Recipient email list',
|
|
nullable: true,
|
|
},
|
|
thread_id: { type: 'string', description: 'Thread ID', nullable: true },
|
|
content_preview: { type: 'string', description: 'Email content preview', nullable: true },
|
|
},
|
|
},
|
|
id: { type: 'string', description: 'Email ID', optional: true },
|
|
subject: { type: 'string', description: 'Email subject', optional: true },
|
|
thread_id: { type: 'string', description: 'Thread ID', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const emailsListOutputs = {
|
|
emails: {
|
|
type: 'array',
|
|
description: 'List of emails',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Email ID', nullable: true },
|
|
subject: { type: 'string', description: 'Email subject', nullable: true },
|
|
from_address_email: { type: 'string', description: 'Sender email', nullable: true },
|
|
lead: { type: 'string', description: 'Lead email', nullable: true },
|
|
thread_id: { type: 'string', description: 'Thread ID', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
count: { type: 'number', description: 'Number of emails returned' },
|
|
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const leadListOutputs = {
|
|
lead_list: {
|
|
type: 'object',
|
|
description: 'Lead list details',
|
|
properties: {
|
|
id: { type: 'string', description: 'Lead list ID', nullable: true },
|
|
organization_id: { type: 'string', description: 'Organization ID', nullable: true },
|
|
has_enrichment_task: {
|
|
type: 'boolean',
|
|
description: 'Whether enrichment is enabled',
|
|
nullable: true,
|
|
},
|
|
owned_by: { type: 'string', description: 'Owner user ID', nullable: true },
|
|
name: { type: 'string', description: 'Lead list name', nullable: true },
|
|
timestamp_created: { type: 'string', description: 'Creation timestamp', nullable: true },
|
|
},
|
|
},
|
|
id: { type: 'string', description: 'Lead list ID', optional: true },
|
|
name: { type: 'string', description: 'Lead list name', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
export const leadListsListOutputs = {
|
|
lead_lists: {
|
|
type: 'array',
|
|
description: 'List of lead lists',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Lead list ID', nullable: true },
|
|
name: { type: 'string', description: 'Lead list name', nullable: true },
|
|
has_enrichment_task: {
|
|
type: 'boolean',
|
|
description: 'Whether enrichment is enabled',
|
|
nullable: true,
|
|
},
|
|
timestamp_created: { type: 'string', description: 'Creation timestamp', nullable: true },
|
|
},
|
|
},
|
|
},
|
|
count: { type: 'number', description: 'Number of lead lists returned' },
|
|
next_starting_after: { type: 'string', description: 'Cursor for the next page', optional: true },
|
|
} satisfies ToolConfig['outputs']
|
|
|
|
async function parseJsonResponse(response: Response): Promise<unknown> {
|
|
try {
|
|
return await response.json()
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function getMessage(value: unknown): string | null {
|
|
const data = asRecord(value)
|
|
return typeof data.message === 'string' ? data.message : null
|
|
}
|
|
|
|
function extractInstantlyError(value: unknown, fallback: string): string {
|
|
const data = asRecord(value)
|
|
if (typeof data.message === 'string') return data.message
|
|
if (typeof data.error === 'string') return data.error
|
|
return fallback
|
|
}
|
|
|
|
function asString(value: unknown): string | null {
|
|
return typeof value === 'string' ? value : null
|
|
}
|
|
|
|
function asNumber(value: unknown): number | null {
|
|
return typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
}
|
|
|
|
function asBoolean(value: unknown): boolean | null {
|
|
return typeof value === 'boolean' ? value : null
|
|
}
|