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<Array<SequenceSummary>> {
const items = await kitListAll<JsonRecord>({
...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<JsonRecord>({
...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<JsonRecord>({
...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: '<p>Welcome</p>',
* 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<JsonRecord>({
...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<JsonRecord>({
...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<JsonRecord>({
...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<JsonRecord>({
...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<string, unknown> = {}) {
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.',
)
}
}