import {
contactSummary,
mutationPreview,
parseAction,
resendRequest,
unwrapList,
type MutationGuardInput,
type ResendAuthOptions,
} from './resend-core.ts'
export type ListContactsInput = ResendAuthOptions & {
limit?: number
after?: string
before?: string
segment_id?: string
}
/** GET /contacts — audience contacts as compact summaries. */
export async function listContacts(input: ListContactsInput = {}) {
const body = await resendRequest({
path: '/contacts',
query: {
limit: input.limit,
after: input.after,
before: input.before,
segment_id: input.segment_id,
},
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return unwrapList(body).map((contact) => contactSummary(contact)).filter(Boolean)
}
/** GET /contacts/{id} — by id or email. */
export async function getContact(input: ResendAuthOptions & { idOrEmail: string }) {
if (!input.idOrEmail) throw new Error('getContact: idOrEmail is required')
return await resendRequest({
path: '/contacts/' + encodeURIComponent(input.idOrEmail),
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
export type CreateContactInput = MutationGuardInput & {
email: string
first_name?: string
last_name?: string
unsubscribed?: boolean
properties?: Record<string, unknown>
}
/** POST /contacts — add a person to the audience. */
export async function createContact(input: CreateContactInput) {
if (!input.email) throw new Error('createContact: email is required')
const body = {
email: input.email,
first_name: input.first_name,
last_name: input.last_name,
unsubscribed: input.unsubscribed,
properties: input.properties,
}
const preview = mutationPreview(input, {
action: 'create contact ' + input.email,
method: 'POST',
path: '/contacts',
body,
})
if (preview) return preview
return await resendRequest({
path: '/contacts',
method: 'POST',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
export type UpdateContactInput = MutationGuardInput & {
idOrEmail: string
first_name?: string
last_name?: string
unsubscribed?: boolean
properties?: Record<string, unknown>
}
/** PATCH /contacts/{id} */
export async function updateContact(input: UpdateContactInput) {
if (!input.idOrEmail) throw new Error('updateContact: idOrEmail is required')
const body = {
first_name: input.first_name,
last_name: input.last_name,
unsubscribed: input.unsubscribed,
properties: input.properties,
}
const preview = mutationPreview(input, {
action: 'update contact ' + input.idOrEmail,
method: 'PATCH',
path: '/contacts/' + input.idOrEmail,
body,
})
if (preview) return preview
return await resendRequest({
path: '/contacts/' + encodeURIComponent(input.idOrEmail),
method: 'PATCH',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** Mark a contact unsubscribed without deleting the record. */
export async function unsubscribeContact(input: MutationGuardInput & { idOrEmail: string }) {
return await updateContact({ ...input, unsubscribed: true })
}
/** DELETE /contacts/{id}. Destructive; requires confirm: true. */
export async function deleteContact(input: MutationGuardInput & { idOrEmail: string }) {
if (!input.idOrEmail) throw new Error('deleteContact: idOrEmail is required')
const preview = mutationPreview(input, {
action: 'delete contact ' + input.idOrEmail,
method: 'DELETE',
path: '/contacts/' + input.idOrEmail,
requireConfirm: true,
})
if (preview) return preview
return await resendRequest({
path: '/contacts/' + encodeURIComponent(input.idOrEmail),
method: 'DELETE',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** GET /segments */
export async function listSegments(input: ResendAuthOptions = {}) {
const body = await resendRequest({
path: '/segments',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return unwrapList(body)
}
/** GET /segments/{id} */
export async function getSegment(input: ResendAuthOptions & { segmentId: string }) {
if (!input.segmentId) throw new Error('getSegment: segmentId is required')
return await resendRequest({
path: '/segments/' + encodeURIComponent(input.segmentId),
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** POST /segments */
export async function createSegment(input: MutationGuardInput & { name: string }) {
if (!input.name) throw new Error('createSegment: name is required')
const body = { name: input.name }
const preview = mutationPreview(input, {
action: 'create segment ' + input.name,
method: 'POST',
path: '/segments',
body,
})
if (preview) return preview
return await resendRequest({
path: '/segments',
method: 'POST',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** DELETE /segments/{id}. Destructive; requires confirm: true. */
export async function deleteSegment(input: MutationGuardInput & { segmentId: string }) {
if (!input.segmentId) throw new Error('deleteSegment: segmentId is required')
const preview = mutationPreview(input, {
action: 'delete segment ' + input.segmentId,
method: 'DELETE',
path: '/segments/' + input.segmentId,
requireConfirm: true,
})
if (preview) return preview
return await resendRequest({
path: '/segments/' + encodeURIComponent(input.segmentId),
method: 'DELETE',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** GET /contacts/{id}/segments */
export async function listContactSegments(input: ResendAuthOptions & { idOrEmail: string }) {
if (!input.idOrEmail) throw new Error('listContactSegments: idOrEmail is required')
const body = await resendRequest({
path: '/contacts/' + encodeURIComponent(input.idOrEmail) + '/segments',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return unwrapList(body)
}
/** POST /contacts/{id}/segments/{segment_id} */
export async function addContactToSegment(
input: MutationGuardInput & { idOrEmail: string; segmentId: string },
) {
if (!input.idOrEmail) throw new Error('addContactToSegment: idOrEmail is required')
if (!input.segmentId) throw new Error('addContactToSegment: segmentId is required')
const path = '/contacts/' + input.idOrEmail + '/segments/' + input.segmentId
const preview = mutationPreview(input, {
action: 'add contact ' + input.idOrEmail + ' to segment ' + input.segmentId,
method: 'POST',
path,
})
if (preview) return preview
return await resendRequest({
path:
'/contacts/' +
encodeURIComponent(input.idOrEmail) +
'/segments/' +
encodeURIComponent(input.segmentId),
method: 'POST',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** DELETE /contacts/{id}/segments/{segment_id} */
export async function removeContactFromSegment(
input: MutationGuardInput & { idOrEmail: string; segmentId: string },
) {
if (!input.idOrEmail) throw new Error('removeContactFromSegment: idOrEmail is required')
if (!input.segmentId) throw new Error('removeContactFromSegment: segmentId is required')
const path = '/contacts/' + input.idOrEmail + '/segments/' + input.segmentId
const preview = mutationPreview(input, {
action: 'remove contact ' + input.idOrEmail + ' from segment ' + input.segmentId,
method: 'DELETE',
path,
})
if (preview) return preview
return await resendRequest({
path:
'/contacts/' +
encodeURIComponent(input.idOrEmail) +
'/segments/' +
encodeURIComponent(input.segmentId),
method: 'DELETE',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** GET /topics */
export async function listTopics(input: ResendAuthOptions = {}) {
const body = await resendRequest({
path: '/topics',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return unwrapList(body)
}
/** GET /topics/{id} */
export async function getTopic(input: ResendAuthOptions & { topicId: string }) {
if (!input.topicId) throw new Error('getTopic: topicId is required')
return await resendRequest({
path: '/topics/' + encodeURIComponent(input.topicId),
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** POST /topics */
export async function createTopic(input: MutationGuardInput & { name: string }) {
if (!input.name) throw new Error('createTopic: name is required')
const body = { name: input.name }
const preview = mutationPreview(input, {
action: 'create topic ' + input.name,
method: 'POST',
path: '/topics',
body,
})
if (preview) return preview
return await resendRequest({
path: '/topics',
method: 'POST',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
/** GET /contacts/{id}/topics */
export async function listContactTopics(input: ResendAuthOptions & { idOrEmail: string }) {
if (!input.idOrEmail) throw new Error('listContactTopics: idOrEmail is required')
const body = await resendRequest({
path: '/contacts/' + encodeURIComponent(input.idOrEmail) + '/topics',
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
return unwrapList(body)
}
export type UpdateContactTopicsInput = MutationGuardInput & {
idOrEmail: string
topics: Array<{ id: string; subscribed: boolean }>
}
/** PATCH /contacts/{id}/topics */
export async function updateContactTopics(input: UpdateContactTopicsInput) {
if (!input.idOrEmail) throw new Error('updateContactTopics: idOrEmail is required')
if (!Array.isArray(input.topics)) {
throw new Error('updateContactTopics: pass an array of { id, subscribed }')
}
const body = { topics: input.topics }
const preview = mutationPreview(input, {
action: 'update contact topics ' + input.idOrEmail,
method: 'PATCH',
path: '/contacts/' + input.idOrEmail + '/topics',
body,
})
if (preview) return preview
return await resendRequest({
path: '/contacts/' + encodeURIComponent(input.idOrEmail) + '/topics',
method: 'PATCH',
body,
account: input.account,
secretName: input.secretName,
integration: input.integration,
})
}
const contactActions = [
'list-contacts',
'get-contact',
'create-contact',
'update-contact',
'unsubscribe-contact',
'delete-contact',
'list-segments',
'get-segment',
'create-segment',
'delete-segment',
'list-contact-segments',
'add-contact-to-segment',
'remove-contact-from-segment',
'list-topics',
'get-topic',
'create-topic',
'list-contact-topics',
'update-contact-topics',
] as const
/** Audience dispatcher. Defaults to list-contacts. */
export default async function contacts(input: Record<string, unknown> = {}) {
const action = parseAction(input.action, contactActions, 'list-contacts', 'contacts')
switch (action) {
case 'list-contacts':
return await listContacts(input as ListContactsInput)
case 'get-contact':
return await getContact(input as never)
case 'create-contact':
return await createContact(input as CreateContactInput)
case 'update-contact':
return await updateContact(input as UpdateContactInput)
case 'unsubscribe-contact':
return await unsubscribeContact(input as never)
case 'delete-contact':
return await deleteContact(input as never)
case 'list-segments':
return await listSegments(input)
case 'get-segment':
return await getSegment(input as never)
case 'create-segment':
return await createSegment(input as never)
case 'delete-segment':
return await deleteSegment(input as never)
case 'list-contact-segments':
return await listContactSegments(input as never)
case 'add-contact-to-segment':
return await addContactToSegment(input as never)
case 'remove-contact-from-segment':
return await removeContactFromSegment(input as never)
case 'list-topics':
return await listTopics(input)
case 'get-topic':
return await getTopic(input as never)
case 'create-topic':
return await createTopic(input as never)
case 'list-contact-topics':
return await listContactTopics(input as never)
case 'update-contact-topics':
return await updateContactTopics(input as UpdateContactTopicsInput)
default: {
const exhaustive: never = action
throw new Error('Unhandled contacts action: ' + String(exhaustive))
}
}
}