import {
broadcastSummary,
DASHBOARD_URL,
mutationPreview,
parseAction,
resendRequest,
unwrapList,
type MutationGuardInput,
type ResendAuthOptions,
} from './resend-core.ts'
export type CreateBroadcastDraftInput = MutationGuardInput & {
from: string
subject: string
html?: string
text?: string
name?: string
reply_to?: string | string[]
preview_text?: string
segment_id?: string
audience_id?: string
topic_id?: string
}
/**
* POST /broadcasts — create a DRAFT. Nothing emails until sendBroadcast
* (or a human sends it from the dashboard `editUrl`).
*/
export async function createBroadcastDraft(input: CreateBroadcastDraftInput) {
if (!input.from) throw new Error('createBroadcastDraft: from is required')
if (!input.subject) throw new Error('createBroadcastDraft: subject is required')
if (!input.segment_id && !input.audience_id) {
throw new Error('createBroadcastDraft: provide segment_id (or legacy audience_id)')
}
if (!input.html && !input.text) {
throw new Error('createBroadcastDraft: provide html or text')
}
const body = {
from: input.from,
subject: input.subject,
html: input.html,
text: input.text,
name: input.name,
reply_to: input.reply_to,
preview_text: input.preview_text,
segment_id: input.segment_id,
audience_id: input.audience_id,
topic_id: input.topic_id,
}
const preview = mutationPreview(input, {
action: 'create broadcast draft',
method: 'POST',
path: '/broadcasts',
body,
})
if (preview) return preview
const created = await resendRequest({
path: '/broadcasts',
method: 'POST',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return created?.id ? { ...created, editUrl: DASHBOARD_URL + '/broadcasts/' + created.id } : created
}
/** GET /broadcasts */
export async function listBroadcasts(input: ResendAuthOptions = {}) {
const body = await resendRequest({
path: '/broadcasts',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return unwrapList(body).map((broadcast) => broadcastSummary(broadcast)).filter(Boolean)
}
/** GET /broadcasts/{id} */
export async function getBroadcast(input: ResendAuthOptions & { broadcastId: string }) {
if (!input.broadcastId) throw new Error('getBroadcast: broadcastId is required')
return await resendRequest({
path: '/broadcasts/' + encodeURIComponent(input.broadcastId),
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
export type UpdateBroadcastInput = MutationGuardInput & {
broadcastId: string
from?: string
subject?: string
html?: string
text?: string
name?: string
reply_to?: string | string[]
preview_text?: string
segment_id?: string
}
/** PATCH /broadcasts/{id} */
export async function updateBroadcast(input: UpdateBroadcastInput) {
if (!input.broadcastId) throw new Error('updateBroadcast: broadcastId is required')
const body = {
from: input.from,
subject: input.subject,
html: input.html,
text: input.text,
name: input.name,
reply_to: input.reply_to,
preview_text: input.preview_text,
segment_id: input.segment_id,
}
const preview = mutationPreview(input, {
action: 'update broadcast ' + input.broadcastId,
method: 'PATCH',
path: '/broadcasts/' + input.broadcastId,
body,
})
if (preview) return preview
return await resendRequest({
path: '/broadcasts/' + encodeURIComponent(input.broadcastId),
method: 'PATCH',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/**
* POST /broadcasts/{id}/send — emails the whole segment. Requires confirm.
* Pass scheduledAt to schedule instead of sending immediately.
*/
export async function sendBroadcast(
input: MutationGuardInput & { broadcastId: string; scheduledAt?: string },
) {
if (!input.broadcastId) throw new Error('sendBroadcast: broadcastId is required')
const body = input.scheduledAt ? { scheduled_at: input.scheduledAt } : {}
const preview = mutationPreview(input, {
action: 'send broadcast ' + input.broadcastId,
method: 'POST',
path: '/broadcasts/' + input.broadcastId + '/send',
body,
requireConfirm: true,
})
if (preview) return preview
return await resendRequest({
path: '/broadcasts/' + encodeURIComponent(input.broadcastId) + '/send',
method: 'POST',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** DELETE /broadcasts/{id}. Destructive; requires confirm: true. */
export async function deleteBroadcast(input: MutationGuardInput & { broadcastId: string }) {
if (!input.broadcastId) throw new Error('deleteBroadcast: broadcastId is required')
const preview = mutationPreview(input, {
action: 'delete broadcast ' + input.broadcastId,
method: 'DELETE',
path: '/broadcasts/' + input.broadcastId,
requireConfirm: true,
})
if (preview) return preview
return await resendRequest({
path: '/broadcasts/' + encodeURIComponent(input.broadcastId),
method: 'DELETE',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
const broadcastActions = [
'list-broadcasts',
'get-broadcast',
'create-broadcast-draft',
'update-broadcast',
'send-broadcast',
'delete-broadcast',
] as const
/** Broadcasts dispatcher. Defaults to list-broadcasts. */
export default async function broadcasts(input: Record<string, unknown> = {}) {
const action = parseAction(input.action, broadcastActions, 'list-broadcasts', 'broadcasts')
switch (action) {
case 'list-broadcasts':
return await listBroadcasts(input)
case 'get-broadcast':
return await getBroadcast(input as never)
case 'create-broadcast-draft':
return await createBroadcastDraft(input as CreateBroadcastDraftInput)
case 'update-broadcast':
return await updateBroadcast(input as UpdateBroadcastInput)
case 'send-broadcast':
return await sendBroadcast(input as never)
case 'delete-broadcast':
return await deleteBroadcast(input as never)
default: {
const exhaustive: never = action
throw new Error('Unhandled broadcasts action: ' + String(exhaustive))
}
}
}