import { kitListAll, kitRequest, unwrapRecord } from './client.ts' import { mutationPreview } from './safety.ts' import { parseAuthInput } from './auth.ts' import { sequenceEmailSummary, sequenceSummary, subscriberSummary } from './models.ts' import type { JsonRecord, KitAuthInput, MutationInput, SequenceSummary } from './types.ts' import { clampInt, compactRecord, optionalBoolean, optionalString, requireId, requireRecord, requireString, } from './types.ts' export type SequenceCreateInput = KitAuthInput & MutationInput & { name: string email_address?: string email_template_id?: number active?: boolean repeat?: boolean } export type SequenceEmailInput = KitAuthInput & MutationInput & { sequenceId: number | string subject: string content?: string published?: boolean preview_text?: string delay_value?: number delay_unit?: string } /** * List sequences. Pass `maxItems` (default 25). * @example * import { listSequences } from 'kody:@kody/kit/sequences' * const sequences = await listSequences({ maxItems: 10 }) */ export async function listSequences( input: KitAuthInput & { maxItems?: number } = {}, ): Promise> { const items = await kitListAll({ ...input, path: '/sequences', itemKey: 'sequences', maxItems: clampInt(input.maxItems, 1, 500, 25), }) return items .map((item) => sequenceSummary(item)) .filter((item): item is SequenceSummary => Boolean(item)) } /** * Get one sequence. * @example * import { getSequence } from 'kody:@kody/kit/sequences' * const sequence = await getSequence({ id: 123 }) */ export async function getSequence(input: KitAuthInput & { id: number | string }) { const id = requireId(input.id, 'id') const result = await kitRequest({ ...input, method: 'GET', path: `/sequences/${id}`, }) return unwrapRecord(result.data, 'sequence', 'getSequence') } /** * Create a sequence. Requires `confirm: true`, or use `dryRun: true`. * Activating a sequence can email subscribers — pass `active` only after * confirmation. * @example * import { createSequence } from 'kody:@kody/kit/sequences' * const preview = await createSequence({ name: 'Welcome', dryRun: true }) */ export async function createSequence(input: SequenceCreateInput) { const name = requireString(input.name, 'name') const body = compactRecord({ name, email_address: input.email_address, email_template_id: input.email_template_id, active: input.active, repeat: input.repeat, }) const preview = mutationPreview(input, { method: 'POST', path: '/sequences', body, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'POST', path: '/sequences', body, confirm: true, }) return unwrapRecord(result.data, 'sequence', 'createSequence') } /** * List emails in a sequence. * @example * import { listSequenceEmails } from 'kody:@kody/kit/sequences' * const emails = await listSequenceEmails({ sequenceId: 123 }) */ export async function listSequenceEmails( input: KitAuthInput & { sequenceId: number | string; maxItems?: number }, ) { const sequenceId = requireId(input.sequenceId, 'sequenceId') return kitListAll({ ...input, path: `/sequences/${sequenceId}/emails`, itemKey: 'emails', maxItems: clampInt(input.maxItems, 1, 500, 50), }) } /** * Create a sequence email. Requires `confirm: true`, or use `dryRun: true`. * @example * import { createSequenceEmail } from 'kody:@kody/kit/sequences' * const preview = await createSequenceEmail({ * sequenceId: 1, * subject: 'Thanks for joining', * content: '

Welcome

', * dryRun: true, * }) */ export async function createSequenceEmail(input: SequenceEmailInput) { const sequenceId = requireId(input.sequenceId, 'sequenceId') const subject = requireString(input.subject, 'subject') const body = compactRecord({ subject, content: input.content, published: input.published, preview_text: input.preview_text, delay_value: input.delay_value, delay_unit: input.delay_unit, }) const preview = mutationPreview(input, { method: 'POST', path: `/sequences/${sequenceId}/emails`, body, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'POST', path: `/sequences/${sequenceId}/emails`, body, confirm: true, }) const email = unwrapRecord(result.data, 'email', 'createSequenceEmail') return { ...sequenceEmailSummary(email), content: email.content } } /** * Get one sequence email. * @example * import { getSequenceEmail } from 'kody:@kody/kit/sequences' * const email = await getSequenceEmail({ sequenceId: 1, emailId: 2 }) */ export async function getSequenceEmail( input: KitAuthInput & { sequenceId: number | string; emailId: number | string }, ) { const sequenceId = requireId(input.sequenceId, 'sequenceId') const emailId = requireId(input.emailId, 'emailId') const result = await kitRequest({ ...input, method: 'GET', path: `/sequences/${sequenceId}/emails/${emailId}`, }) const email = unwrapRecord(result.data, 'email', 'getSequenceEmail') return { ...sequenceEmailSummary(email), preview_text: email.preview_text, content: email.content, delay_value: email.delay_value, delay_unit: email.delay_unit, position: email.position, email_template_id: email.email_template_id, } } /** * Update a sequence email. Requires `confirm: true`, or use `dryRun: true`. * @example * import { updateSequenceEmail } from 'kody:@kody/kit/sequences' * const preview = await updateSequenceEmail({ * sequenceId: 1, * emailId: 2, * subject: 'Updated', * dryRun: true, * }) */ export async function updateSequenceEmail( input: KitAuthInput & MutationInput & { sequenceId: number | string emailId: number | string subject?: string content?: string published?: boolean }, ) { const sequenceId = requireId(input.sequenceId, 'sequenceId') const emailId = requireId(input.emailId, 'emailId') const body = compactRecord({ subject: input.subject, content: input.content, published: input.published, }) const preview = mutationPreview(input, { method: 'PUT', path: `/sequences/${sequenceId}/emails/${emailId}`, body, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'PUT', path: `/sequences/${sequenceId}/emails/${emailId}`, body, confirm: true, }) const email = unwrapRecord(result.data, 'email', 'updateSequenceEmail') return { ...sequenceEmailSummary(email), content: email.content } } /** * Enroll a subscriber in a sequence by email. Requires `confirm: true`, or * use `dryRun: true`. An active sequence may email immediately. * @example * import { addSubscriberToSequence } from 'kody:@kody/kit/sequences' * const preview = await addSubscriberToSequence({ * sequenceId: 1, * email_address: 'ada@example.com', * dryRun: true, * }) */ export async function addSubscriberToSequence( input: KitAuthInput & MutationInput & { sequenceId: number | string; email_address: string }, ) { const sequenceId = requireId(input.sequenceId, 'sequenceId') const email = requireString(input.email_address, 'email_address') const preview = mutationPreview(input, { method: 'POST', path: `/sequences/${sequenceId}/subscribers`, body: { email_address: email }, }) if (preview) return preview const result = await kitRequest({ ...input, method: 'POST', path: `/sequences/${sequenceId}/subscribers`, body: { email_address: email }, confirm: true, }) return subscriberSummary(unwrapRecord(result.data, 'subscriber', 'addSubscriberToSequence')) } /** * Sequence helpers. Defaults to `list`. * @example * import sequences from 'kody:@kody/kit/sequences' * const all = await sequences({ action: 'list', maxItems: 10 }) */ export default async function sequencesEntrypoint(params: Record = {}) { const input = requireRecord(params, 'sequences') const auth = parseAuthInput(input) const action = optionalString(input.action, 'action') ?? 'list' const mutation = { confirm: optionalBoolean(input.confirm, 'confirm'), dryRun: optionalBoolean(input.dryRun, 'dryRun'), } switch (action) { case 'list': return listSequences({ ...auth, maxItems: input.maxItems as number | undefined }) case 'get': return getSequence({ ...auth, id: requireId(input.id, 'id') }) case 'create': return createSequence({ ...auth, ...mutation, name: requireString(input.name, 'name'), email_address: optionalString(input.email_address, 'email_address'), active: optionalBoolean(input.active, 'active'), }) case 'emails': return listSequenceEmails({ ...auth, sequenceId: requireId(input.sequenceId ?? input.id, 'sequenceId'), }) case 'create-email': return createSequenceEmail({ ...auth, ...mutation, sequenceId: requireId(input.sequenceId, 'sequenceId'), subject: requireString(input.subject, 'subject'), content: optionalString(input.content, 'content'), }) case 'get-email': return getSequenceEmail({ ...auth, sequenceId: requireId(input.sequenceId, 'sequenceId'), emailId: requireId(input.emailId, 'emailId'), }) case 'update-email': return updateSequenceEmail({ ...auth, ...mutation, sequenceId: requireId(input.sequenceId, 'sequenceId'), emailId: requireId(input.emailId, 'emailId'), subject: optionalString(input.subject, 'subject'), content: optionalString(input.content, 'content'), published: optionalBoolean(input.published, 'published'), }) case 'add-subscriber': return addSubscriberToSequence({ ...auth, ...mutation, sequenceId: requireId(input.sequenceId, 'sequenceId'), email_address: requireString(input.email_address, 'email_address'), }) default: throw new Error( 'sequences action must be one of: list, get, create, emails, create-email, get-email, update-email, add-subscriber.', ) } }