Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/resend

src/templates.ts

180 lines · 5.1 KB · TypeScript
import {
	mutationPreview,
	parseAction,
	resendRequest,
	unwrapList,
	type MutationGuardInput,
	type ResendAuthOptions,
} from './resend-core.ts'

/** GET /templates */
export async function listTemplates(input: ResendAuthOptions = {}) {
	const body = await resendRequest({
		path: '/templates',
		account: input.account,
		secretName: input.secretName,
		integration: input.integration,
	})
	return unwrapList(body)
}

/** GET /templates/{id} */
export async function getTemplate(input: ResendAuthOptions & { templateId: string }) {
	if (!input.templateId) throw new Error('getTemplate: templateId is required')
	return await resendRequest({
		path: '/templates/' + encodeURIComponent(input.templateId),
		account: input.account,
		secretName: input.secretName,
		integration: input.integration,
	})
}

export type CreateTemplateInput = MutationGuardInput & {
	name: string
	html: string
	subject?: string
	text?: string
	from?: string
	reply_to?: string | string[]
	variables?: Array<{ key: string; type?: string; fallback_value?: unknown }>
}

/** POST /templates — create a draft. Publish explicitly before sending. */
export async function createTemplate(input: CreateTemplateInput) {
	if (!input.name) throw new Error('createTemplate: name is required')
	if (!input.html) throw new Error('createTemplate: html is required')
	const body = {
		name: input.name,
		html: input.html,
		subject: input.subject,
		text: input.text,
		from: input.from,
		reply_to: input.reply_to,
		variables: input.variables,
	}
	const preview = mutationPreview(input, {
		action: 'create template ' + input.name,
		method: 'POST',
		path: '/templates',
		body,
	})
	if (preview) return preview
	return await resendRequest({
		path: '/templates',
		method: 'POST',
		body,
		account: input.account,
		secretName: input.secretName,
		integration: input.integration,
	})
}

export type UpdateTemplateInput = MutationGuardInput & {
	templateId: string
	name?: string
	html?: string
	subject?: string
	text?: string
	from?: string
	reply_to?: string | string[]
	variables?: Array<{ key: string; type?: string; fallback_value?: unknown }>
}

/** PATCH /templates/{id} — draft changes until publish. */
export async function updateTemplate(input: UpdateTemplateInput) {
	if (!input.templateId) throw new Error('updateTemplate: templateId is required')
	const body = {
		name: input.name,
		html: input.html,
		subject: input.subject,
		text: input.text,
		from: input.from,
		reply_to: input.reply_to,
		variables: input.variables,
	}
	const preview = mutationPreview(input, {
		action: 'update template ' + input.templateId,
		method: 'PATCH',
		path: '/templates/' + input.templateId,
		body,
	})
	if (preview) return preview
	return await resendRequest({
		path: '/templates/' + encodeURIComponent(input.templateId),
		method: 'PATCH',
		body,
		account: input.account,
		secretName: input.secretName,
		integration: input.integration,
	})
}

/** POST /templates/{id}/publish — make the latest draft live for sending. */
export async function publishTemplate(input: MutationGuardInput & { templateId: string }) {
	if (!input.templateId) throw new Error('publishTemplate: templateId is required')
	const preview = mutationPreview(input, {
		action: 'publish template ' + input.templateId,
		method: 'POST',
		path: '/templates/' + input.templateId + '/publish',
		requireConfirm: true,
	})
	if (preview) return preview
	return await resendRequest({
		path: '/templates/' + encodeURIComponent(input.templateId) + '/publish',
		method: 'POST',
		account: input.account,
		secretName: input.secretName,
		integration: input.integration,
	})
}

/** DELETE /templates/{id}. Destructive; requires confirm: true. */
export async function deleteTemplate(input: MutationGuardInput & { templateId: string }) {
	if (!input.templateId) throw new Error('deleteTemplate: templateId is required')
	const preview = mutationPreview(input, {
		action: 'delete template ' + input.templateId,
		method: 'DELETE',
		path: '/templates/' + input.templateId,
		requireConfirm: true,
	})
	if (preview) return preview
	return await resendRequest({
		path: '/templates/' + encodeURIComponent(input.templateId),
		method: 'DELETE',
		account: input.account,
		secretName: input.secretName,
		integration: input.integration,
	})
}

const templateActions = [
	'list-templates',
	'get-template',
	'create-template',
	'update-template',
	'publish-template',
	'delete-template',
] as const

/** Templates dispatcher. Defaults to list-templates. */
export default async function templates(input: Record<string, unknown> = {}) {
	const action = parseAction(input.action, templateActions, 'list-templates', 'templates')
	switch (action) {
		case 'list-templates':
			return await listTemplates(input)
		case 'get-template':
			return await getTemplate(input as never)
		case 'create-template':
			return await createTemplate(input as CreateTemplateInput)
		case 'update-template':
			return await updateTemplate(input as UpdateTemplateInput)
		case 'publish-template':
			return await publishTemplate(input as never)
		case 'delete-template':
			return await deleteTemplate(input as never)
		default: {
			const exhaustive: never = action
			throw new Error('Unhandled templates action: ' + String(exhaustive))
		}
	}
}