import { CLOSE_ACTIVITY_SCHEMA, CLOSE_CALL_SCHEMA, CLOSE_EMAIL_SCHEMA, CLOSE_NOTE_SCHEMA, CLOSE_SMS_SCHEMA, CLOSE_VALUE_SCHEMA, closePageSchema, } from "@automate.ax/integration-contracts/close" import * as z from "zod" import { defineAction } from "../../../automation/actions" import { CLOSE_ACCOUNT, getCloseApi, CLOSE_DATE_INPUT_SCHEMA, CLOSE_DATE_OR_DAY_INPUT_SCHEMA, CLOSE_ID_SCHEMA, CLOSE_PAGINATION_SCHEMA, hasCloseUpdate, } from "./lib" const NOTE_ATTACHMENT_SCHEMA = z.object({ contentType: z.string().nullable().optional(), filename: z.string().trim().min(1), url: z.url().startsWith("https://app.close.com/go/file/"), }) const EMAIL_ATTACHMENT_SCHEMA = z.object({ contentId: z.string().nullable().optional(), contentType: z.string().nullable().optional(), filename: z.string().trim().min(1), inlineOnly: z.boolean().optional(), size: z.number().int().nonnegative(), url: z.url().startsWith("https://app.close.com/go/file/"), }) const EMAIL_ADDRESS_SCHEMA = z.email().max(1_000) const EMAIL_ATTACHMENTS_SCHEMA = EMAIL_ATTACHMENT_SCHEMA.array().max(1_000) const EMAIL_RECIPIENTS_SCHEMA = EMAIL_ADDRESS_SCHEMA.array().max(500) const EMAIL_BODY_SCHEMA = z.string().max(1_048_576) const EMAIL_SUBJECT_SCHEMA = z.string().max(1_000) const SMS_ATTACHMENT_CONTENT_TYPE_SCHEMA = z.enum([ "application/pdf", "application/vcard", "audio/3gpp", "audio/3gpp2", "audio/L24", "audio/ac3", "audio/amr", "audio/amr-nb", "audio/basic", "audio/mp4", "audio/mpeg", "audio/ogg", "audio/vnd.rn-realaudio", "audio/vnd.wave", "audio/webm", "image/bmp", "image/gif", "image/jpeg", "image/jpg", "image/png", "image/tiff", "text/calendar", "text/csv", "text/directory", "text/richtext", "text/rtf", "text/vcard", "text/x-vcard", "video/3gpp", "video/3gpp-tt", "video/3gpp2", "video/H261", "video/H263", "video/H263-1998", "video/H263-2000", "video/H264", "video/mp4", "video/mpeg", "video/quicktime", "video/webm", ]) const NOTE_FIELDS = { activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), attachments: NOTE_ATTACHMENT_SCHEMA.array().max(1_000).nullable().optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), note: z.string().nullable().optional(), noteHtml: z.string().nullable().optional(), pinned: z.boolean().nullable().optional(), title: z.string().nullable().optional(), } const COMMUNICATION_STATUS_SCHEMA = z.enum([ "draft", "error", "inbox", "outbox", "scheduled", "sent", ]) const ACTIVITY_FILTER_FIELDS = { ...CLOSE_PAGINATION_SCHEMA, activityAtAfter: CLOSE_DATE_OR_DAY_INPUT_SCHEMA.optional(), activityAtBefore: CLOSE_DATE_OR_DAY_INPUT_SCHEMA.optional(), contactIds: CLOSE_ID_SCHEMA.array().optional(), createdAfter: CLOSE_DATE_OR_DAY_INPUT_SCHEMA.optional(), createdBefore: CLOSE_DATE_OR_DAY_INPUT_SCHEMA.optional(), ids: CLOSE_ID_SCHEMA.array().optional(), leadIds: CLOSE_ID_SCHEMA.array().optional(), userIds: CLOSE_ID_SCHEMA.array().optional(), } const CALL_UPDATE_FIELDS = { activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), conversationTypeId: CLOSE_ID_SCHEMA.nullable().optional(), duration: z.number().int().nonnegative().max(604_800).nullable().optional(), leadId: CLOSE_ID_SCHEMA.nullable().optional(), note: z.string().nullable().optional(), noteHtml: z.string().nullable().optional(), outcomeId: CLOSE_ID_SCHEMA.nullable().optional(), phone: z.string().trim().min(1).nullable().optional(), playbookId: CLOSE_ID_SCHEMA.nullable().optional(), qualityInfo: z.string().nullable().optional(), recordingUrl: z.url().startsWith("https://").nullable().optional(), status: z .enum([ "busy", "cancel", "completed", "created", "failed", "in-progress", "no-answer", "timeout", ]) .optional(), userId: CLOSE_ID_SCHEMA.nullable().optional(), voicemailUrl: z.url().startsWith("https://").nullable().optional(), } const EMAIL_UPDATE_FIELDS = { activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), attachments: EMAIL_ATTACHMENTS_SCHEMA.nullable().optional(), bcc: EMAIL_RECIPIENTS_SCHEMA.nullable().optional(), bodyHtml: EMAIL_BODY_SCHEMA.nullable().optional(), bodyText: EMAIL_BODY_SCHEMA.nullable().optional(), cc: EMAIL_RECIPIENTS_SCHEMA.nullable().optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), emailAccountId: CLOSE_ID_SCHEMA.nullable().optional(), followupDate: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), followupSequenceAddCcBcc: z.boolean().nullable().optional(), followupSequenceDelay: z.number().int().min(86_400).nullable().optional(), followupSequenceId: CLOSE_ID_SCHEMA.nullable().optional(), inReplyToId: CLOSE_ID_SCHEMA.nullable().optional(), opens: z .object({ openedAt: CLOSE_DATE_INPUT_SCHEMA }) .array() .nullable() .optional(), sender: z.string().nullable().optional(), status: COMMUNICATION_STATUS_SCHEMA.optional(), subject: EMAIL_SUBJECT_SCHEMA.nullable().optional(), templateId: CLOSE_ID_SCHEMA.nullable().optional(), to: EMAIL_RECIPIENTS_SCHEMA.nullable().optional(), userId: CLOSE_ID_SCHEMA.nullable().optional(), } const SMS_ATTACHMENT_SCHEMA = z.object({ contentType: SMS_ATTACHMENT_CONTENT_TYPE_SCHEMA, filename: z.string().trim().min(1), url: z.url().startsWith("https://app.close.com/go/file/"), }) const SMS_UPDATE_FIELDS = { activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), attachments: SMS_ATTACHMENT_SCHEMA.array().max(10).nullable().optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), dateScheduled: CLOSE_DATE_INPUT_SCHEMA.optional(), leadId: CLOSE_ID_SCHEMA.nullable().optional(), localPhone: z.string().trim().min(1).nullable().optional(), remotePhone: z.string().trim().min(1).nullable().optional(), status: COMMUNICATION_STATUS_SCHEMA.optional(), templateId: CLOSE_ID_SCHEMA.nullable().optional(), text: z.string().nullable().optional(), userId: CLOSE_ID_SCHEMA.nullable().optional(), } const SIGNED_FILE_UPLOAD_SCHEMA = z.object({ download: z.object({ url: z.url() }), upload: z.object({ fields: z.record(z.string(), z.string()), url: z.url(), }), }) /** * Converts public activity filters to Close's provider query parameters. * * @param input - Parsed common activity filters. */ function activityListQuery( input: z.output>, ) { return { _limit: input.limit, _skip: input.skip, activity_at__gte: closeDateFilter(input.activityAtAfter), activity_at__lte: closeDateFilter(input.activityAtBefore), contact_id: input.contactIds, date_created__gte: closeDateFilter(input.createdAfter), date_created__lte: closeDateFilter(input.createdBefore), id__in: input.ids, lead_id: input.leadIds, user_id: input.userIds, } } /** * Serializes a public date filter for Close. * * @param value - Date or provider-compatible date string. */ function closeDateFilter(value: Date | string | undefined) { return value instanceof Date ? value.toISOString() : value } /** Lists all Close activity types for a lead or organization. */ export const listCloseActivities = defineAction("List Close activities") .describe( "Lists Close calls, emails, notes, SMS, meetings, and other activities.", ) .account(CLOSE_ACCOUNT) .input( z.object({ ...CLOSE_PAGINATION_SCHEMA, contactId: CLOSE_ID_SCHEMA.optional(), leadId: CLOSE_ID_SCHEMA.optional(), type: z.string().optional(), userId: CLOSE_ID_SCHEMA.optional(), }), ) .output(closePageSchema(CLOSE_ACTIVITY_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/", { query: { _limit: input.limit, _skip: input.skip, _type: input.type, contact_id: input.contactId, lead_id: input.leadId, user_id: input.userId, }, responseSchema: closePageSchema(CLOSE_ACTIVITY_SCHEMA), }), ) /** Uploads a file for use in Close activity and opportunity attachments. */ export const uploadCloseFile = defineAction("Upload Close file") .describe("Uploads a file and returns a temporary Close attachment URL.") .account(CLOSE_ACCOUNT) .input( z .object({ contentType: z.string().trim().min(1).optional(), file: z.instanceof(Blob), filename: z.string().trim().min(1), }) .refine(({ contentType, file }) => Boolean(contentType || file.type), { message: "Provide contentType when the file has no media type.", path: ["contentType"], }), ) .output( z.object({ contentType: z.string(), filename: z.string(), size: z.number().int().nonnegative(), url: z.url(), }), ) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const contentType = input.contentType || input.file.type const signed = await getCloseApi(account.secret).request("files/upload/", { body: { contentType, filename: input.filename }, method: "POST", responseKeyFormat: "provider", responseSchema: SIGNED_FILE_UPLOAD_SCHEMA, }) const form = new FormData() for (const [key, value] of Object.entries(signed.upload.fields)) { form.append(key, value) } form.append("file", input.file, input.filename) const response = await fetch(signed.upload.url, { body: form, method: "POST", }) if (response.status !== 201) { throw new Error(`Close file upload failed (${response.status}).`) } return { contentType, filename: input.filename, size: input.file.size, url: signed.download.url, } }) /** Retrieves one Close note activity. */ export const getCloseNote = defineAction("Get Close note") .describe("Retrieves one Close note activity by ID.") .account(CLOSE_ACCOUNT) .input(z.object({ noteId: CLOSE_ID_SCHEMA })) .output(CLOSE_NOTE_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request( `activity/note/${encodeURIComponent(input.noteId)}/`, { responseSchema: CLOSE_NOTE_SCHEMA }, ), ) /** Creates a plaintext or rich-text Close note. */ export const createCloseNote = defineAction("Create Close note") .describe("Creates a Close note with rich text, attachments, and pinning.") .account(CLOSE_ACCOUNT) .input(z.object({ leadId: CLOSE_ID_SCHEMA, ...NOTE_FIELDS })) .output(CLOSE_NOTE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/note/", { body: input, method: "POST", responseSchema: CLOSE_NOTE_SCHEMA, }), ) /** Updates a Close note. */ export const updateCloseNote = defineAction("Update Close note") .describe("Updates Close note content, attachments, title, or pinning.") .account(CLOSE_ACCOUNT) .input( z .object({ noteId: CLOSE_ID_SCHEMA, ...NOTE_FIELDS }) .refine( (input) => hasCloseUpdate(input, "noteId"), "Provide a note field to update.", ), ) .output(CLOSE_NOTE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input: { noteId, ...body } }) => getCloseApi(account.secret).request( `activity/note/${encodeURIComponent(noteId)}/`, { body, method: "PUT", responseSchema: CLOSE_NOTE_SCHEMA }, ), ) /** Permanently deletes a Close note. */ export const deleteCloseNote = defineAction("Delete Close note") .describe("Permanently deletes one Close note activity.") .account(CLOSE_ACCOUNT) .input(z.object({ noteId: CLOSE_ID_SCHEMA })) .output(z.object({ deleted: z.literal(true), noteId: z.string() })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getCloseApi(account.secret).request( `activity/note/${encodeURIComponent(input.noteId)}/`, { method: "DELETE", responseSchema: CLOSE_VALUE_SCHEMA }, ) return { deleted: true as const, noteId: input.noteId } }) /** Lists Close call activities. */ export const listCloseCalls = defineAction("List Close calls") .describe("Lists Close call activities with common activity filters.") .account(CLOSE_ACCOUNT) .input(z.object(ACTIVITY_FILTER_FIELDS)) .output(closePageSchema(CLOSE_CALL_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/call/", { query: activityListQuery(input), responseSchema: closePageSchema(CLOSE_CALL_SCHEMA), }), ) /** Retrieves one Close call activity. */ export const getCloseCall = defineAction("Get Close call") .describe("Retrieves one Close call activity by ID.") .account(CLOSE_ACCOUNT) .input(z.object({ callId: CLOSE_ID_SCHEMA })) .output(CLOSE_CALL_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request( `activity/call/${encodeURIComponent(input.callId)}/`, { responseSchema: CLOSE_CALL_SCHEMA }, ), ) /** Logs a call made outside Close's built-in calling system. */ export const logCloseCall = defineAction("Log Close call") .describe("Logs an inbound or outbound external call on a Close lead.") .account(CLOSE_ACCOUNT) .input( z.object({ activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), direction: z.enum(["inbound", "outbound"]).optional(), duration: z .number() .int() .nonnegative() .max(604_800) .nullable() .optional(), leadId: CLOSE_ID_SCHEMA.nullable().optional(), note: z.string().nullable().optional(), noteHtml: z.string().nullable().optional(), outcomeId: CLOSE_ID_SCHEMA.nullable().optional(), phone: z.string().trim().min(1).nullable().optional(), recordingUrl: z.url().startsWith("https://").nullable().optional(), status: z .enum([ "busy", "cancel", "completed", "created", "failed", "in-progress", "no-answer", "timeout", ]) .prefault("completed"), userId: CLOSE_ID_SCHEMA.nullable().optional(), }), ) .output(CLOSE_CALL_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/call/", { body: { ...input, source: "External" }, method: "POST", responseSchema: CLOSE_CALL_SCHEMA, }), ) /** Updates a Close call activity. */ export const updateCloseCall = defineAction("Update Close call") .describe("Updates an external Close call activity.") .account(CLOSE_ACCOUNT) .input( z .object({ callId: CLOSE_ID_SCHEMA, ...CALL_UPDATE_FIELDS }) .refine( (input) => hasCloseUpdate(input, "callId"), "Provide a call field to update.", ), ) .output(CLOSE_CALL_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input: { callId, ...body } }) => getCloseApi(account.secret).request( `activity/call/${encodeURIComponent(callId)}/`, { body, method: "PUT", responseSchema: CLOSE_CALL_SCHEMA }, ), ) /** Permanently deletes a Close call activity. */ export const deleteCloseCall = defineAction("Delete Close call") .describe("Permanently deletes one Close call activity.") .account(CLOSE_ACCOUNT) .input(z.object({ callId: CLOSE_ID_SCHEMA })) .output(z.object({ callId: z.string(), deleted: z.literal(true) })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getCloseApi(account.secret).request( `activity/call/${encodeURIComponent(input.callId)}/`, { method: "DELETE", responseSchema: CLOSE_VALUE_SCHEMA }, ) return { callId: input.callId, deleted: true as const } }) /** Lists Close email activities. */ export const listCloseEmails = defineAction("List Close emails") .describe("Lists Close email activities with common activity filters.") .account(CLOSE_ACCOUNT) .input(z.object(ACTIVITY_FILTER_FIELDS)) .output(closePageSchema(CLOSE_EMAIL_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/email/", { query: activityListQuery(input), responseSchema: closePageSchema(CLOSE_EMAIL_SCHEMA), }), ) /** Retrieves one Close email activity. */ export const getCloseEmail = defineAction("Get Close email") .describe("Retrieves one Close email activity by ID.") .account(CLOSE_ACCOUNT) .input(z.object({ emailId: CLOSE_ID_SCHEMA })) .output(CLOSE_EMAIL_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request( `activity/email/${encodeURIComponent(input.emailId)}/`, { responseSchema: CLOSE_EMAIL_SCHEMA }, ), ) /** Creates, schedules, sends, or logs a Close email activity. */ export const createCloseEmail = defineAction("Create Close email") .describe( "Creates a Close email draft, schedules or sends it, or logs an email.", ) .account(CLOSE_ACCOUNT) .input( z .object({ activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), attachments: EMAIL_ATTACHMENTS_SCHEMA.nullable().optional(), bcc: EMAIL_RECIPIENTS_SCHEMA.nullable().optional(), bodyHtml: EMAIL_BODY_SCHEMA.nullable().optional(), bodyText: EMAIL_BODY_SCHEMA.nullable().optional(), cc: EMAIL_RECIPIENTS_SCHEMA.nullable().optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), dateScheduled: CLOSE_DATE_INPUT_SCHEMA.optional(), emailAccountId: CLOSE_ID_SCHEMA.nullable().optional(), followupDate: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), followupSequenceAddCcBcc: z.boolean().nullable().optional(), followupSequenceDelay: z .number() .int() .min(86_400) .nullable() .optional(), followupSequenceId: CLOSE_ID_SCHEMA.nullable().optional(), inReplyToId: CLOSE_ID_SCHEMA.nullable().optional(), leadId: CLOSE_ID_SCHEMA, sendIn: z.number().int().min(0).max(59).optional(), sender: z.string().nullable().optional(), status: COMMUNICATION_STATUS_SCHEMA, subject: EMAIL_SUBJECT_SCHEMA.nullable().optional(), templateId: CLOSE_ID_SCHEMA.nullable().optional(), to: EMAIL_RECIPIENTS_SCHEMA.nullable().optional(), userId: CLOSE_ID_SCHEMA.nullable().optional(), }) .refine( ({ dateScheduled, status }) => status !== "scheduled" || dateScheduled !== undefined, { message: "Scheduled emails require dateScheduled.", path: ["dateScheduled"], }, ) .refine( ({ sender, status }) => !["error", "inbox", "outbox", "scheduled"].includes(status) || Boolean(sender), { message: "This email status requires sender.", path: ["sender"] }, ), ) .output(CLOSE_EMAIL_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/email/", { body: input, method: "POST", responseSchema: CLOSE_EMAIL_SCHEMA, }), ) /** Updates a Close email activity. */ export const updateCloseEmail = defineAction("Update Close email") .describe("Updates a Close email draft or logged email activity.") .account(CLOSE_ACCOUNT) .input( z .object({ emailId: CLOSE_ID_SCHEMA, ...EMAIL_UPDATE_FIELDS }) .refine( (input) => hasCloseUpdate(input, "emailId"), "Provide an email field to update.", ), ) .output(CLOSE_EMAIL_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input: { emailId, ...body } }) => getCloseApi(account.secret).request( `activity/email/${encodeURIComponent(emailId)}/`, { body, method: "PUT", responseSchema: CLOSE_EMAIL_SCHEMA }, ), ) /** Permanently deletes a Close email activity. */ export const deleteCloseEmail = defineAction("Delete Close email") .describe("Permanently deletes one Close email activity.") .account(CLOSE_ACCOUNT) .input(z.object({ emailId: CLOSE_ID_SCHEMA })) .output(z.object({ deleted: z.literal(true), emailId: z.string() })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getCloseApi(account.secret).request( `activity/email/${encodeURIComponent(input.emailId)}/`, { method: "DELETE", responseSchema: CLOSE_VALUE_SCHEMA }, ) return { deleted: true as const, emailId: input.emailId } }) /** Lists Close SMS activities. */ export const listCloseSmsMessages = defineAction("List Close SMS messages") .describe("Lists Close SMS activities with common activity filters.") .account(CLOSE_ACCOUNT) .input(z.object(ACTIVITY_FILTER_FIELDS)) .output(closePageSchema(CLOSE_SMS_SCHEMA)) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request("activity/sms/", { query: activityListQuery(input), responseSchema: closePageSchema(CLOSE_SMS_SCHEMA), }), ) /** Retrieves one Close SMS activity. */ export const getCloseSms = defineAction("Get Close SMS") .describe("Retrieves one Close SMS activity by ID.") .account(CLOSE_ACCOUNT) .input(z.object({ smsId: CLOSE_ID_SCHEMA })) .output(CLOSE_SMS_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => getCloseApi(account.secret).request( `activity/sms/${encodeURIComponent(input.smsId)}/`, { responseSchema: CLOSE_SMS_SCHEMA }, ), ) /** Creates, schedules, sends, or logs a Close SMS activity. */ export const createCloseSms = defineAction("Create Close SMS") .describe("Creates a Close SMS draft, sends it, or logs an external message.") .account(CLOSE_ACCOUNT) .input( z .object({ activityAt: CLOSE_DATE_INPUT_SCHEMA.nullable().optional(), attachments: SMS_ATTACHMENT_SCHEMA.array() .max(10) .nullable() .optional(), contactId: CLOSE_ID_SCHEMA.nullable().optional(), dateScheduled: CLOSE_DATE_INPUT_SCHEMA.optional(), direction: z.enum(["inbound", "outbound"]).optional(), leadId: CLOSE_ID_SCHEMA.nullable().optional(), localPhone: z.string().trim().min(1).nullable().optional(), remotePhone: z.string().trim().min(1).nullable().optional(), sendIn: z.number().int().min(0).max(59).optional(), source: z.enum(["Close.io", "External"]).optional(), status: COMMUNICATION_STATUS_SCHEMA, templateId: CLOSE_ID_SCHEMA.nullable().optional(), text: z.string().nullable().optional(), userId: CLOSE_ID_SCHEMA.nullable().optional(), sendToInbox: z.boolean().optional(), }) .refine( ({ dateScheduled, status }) => status !== "scheduled" || dateScheduled !== undefined, { message: "Scheduled SMS messages require dateScheduled.", path: ["dateScheduled"], }, ) .refine( ({ localPhone, status }) => !["outbox", "scheduled"].includes(status) || Boolean(localPhone), { message: "Sending SMS messages requires localPhone.", path: ["localPhone"], }, ), ) .output(CLOSE_SMS_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input: { sendToInbox, ...body } }) => getCloseApi(account.secret).request("activity/sms/", { body, method: "POST", query: { send_to_inbox: sendToInbox }, responseSchema: CLOSE_SMS_SCHEMA, }), ) /** Modifies a Close SMS draft or cancels a queued SMS. */ export const updateCloseSms = defineAction("Update Close SMS") .describe("Modifies a Close SMS draft or cancels a queued SMS.") .account(CLOSE_ACCOUNT) .input( z .object({ smsId: CLOSE_ID_SCHEMA, ...SMS_UPDATE_FIELDS }) .refine( (input) => hasCloseUpdate(input, "smsId"), "Provide an SMS field to update.", ) .refine( ({ dateScheduled, status }) => status !== "scheduled" || dateScheduled !== undefined, { message: "Scheduled SMS messages require dateScheduled.", path: ["dateScheduled"], }, ), ) .output(CLOSE_SMS_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input: { smsId, ...body } }) => getCloseApi(account.secret).request( `activity/sms/${encodeURIComponent(smsId)}/`, { body, method: "PUT", responseSchema: CLOSE_SMS_SCHEMA }, ), ) /** Permanently deletes a Close SMS activity. */ export const deleteCloseSms = defineAction("Delete Close SMS") .describe("Permanently deletes one Close SMS activity.") .account(CLOSE_ACCOUNT) .input(z.object({ smsId: CLOSE_ID_SCHEMA })) .output(z.object({ deleted: z.literal(true), smsId: z.string() })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getCloseApi(account.secret).request( `activity/sms/${encodeURIComponent(input.smsId)}/`, { method: "DELETE", responseSchema: CLOSE_VALUE_SCHEMA }, ) return { deleted: true as const, smsId: input.smsId } })