import * as z from "zod" import { defineAction } from "../../automation/actions" import { getBrevoApi, parseBrevoResponse, } from "@automate.ax/integration-contracts/brevo" const BREVO_ACCOUNT = "brevo" const CAMPAIGN_ID_SCHEMA = z.number().int().positive() const CAMPAIGN_STATUS_SCHEMA = z.enum([ "draft", "sent", "archive", "queued", "suspended", "inProcess", ]) const STATUS_UPDATE_SCHEMA = z.enum([ "suspended", "archive", "darchive", "sent", "queued", "replicate", "replicateTemplate", "cancel", "draft", ]) const RECIPIENTS_INPUT_SCHEMA = z.object({ exclusionListIds: z.number().int().positive().array().optional(), listIds: z.number().int().positive().array().min(1), }) const SMS_FIELDS = { content: z.string().min(1).optional(), name: z.string().min(1).optional(), organisationPrefix: z.string().optional(), recipients: RECIPIENTS_INPUT_SCHEMA.optional(), scheduledAt: z.iso.datetime({ offset: true }).optional(), sender: z.string().min(1).max(15).optional(), unicodeEnabled: z.boolean().optional(), unsubscribeInstruction: z.string().optional(), } const CREATE_SMS_SCHEMA = z .object({ ...SMS_FIELDS, content: z.string().min(1), name: z.string().min(1), sender: z.string().min(1).max(15), }) .refine( ({ recipients, scheduledAt }) => !scheduledAt || Boolean(recipients), { message: "recipients are required when scheduledAt is provided.", }, ) const UPDATE_SMS_SCHEMA = z.object({ ...SMS_FIELDS, campaignId: CAMPAIGN_ID_SCHEMA, }) const SMS_STATS_SCHEMA = z.object({ answered: z.number().int().nonnegative(), delivered: z.number().int().nonnegative(), hardBounces: z.number().int().nonnegative(), processing: z.number().int().nonnegative(), sent: z.number().int().nonnegative(), softBounces: z.number().int().nonnegative(), unsubscriptions: z.number().int().nonnegative(), }) const SMS_CAMPAIGN_SCHEMA = z.object({ content: z.string(), createdAt: z.string(), id: CAMPAIGN_ID_SCHEMA, modifiedAt: z.string(), name: z.string(), organisationPrefix: z.string().optional(), recipients: z.object({ exclusionLists: z.number().int().array().optional(), lists: z.number().int().array(), }), scheduledAt: z.string().optional(), sender: z.string(), sentDate: z.string().optional(), statistics: SMS_STATS_SCHEMA, status: CAMPAIGN_STATUS_SCHEMA, unsubscribeInstruction: z.string().optional(), }) const SMS_CAMPAIGN_DETAIL_SCHEMA = SMS_CAMPAIGN_SCHEMA.extend({ recipients: z.object({ exclusionLists: z .object({ id: z.number().int().positive().optional(), name: z.string().optional(), }) .array() .optional(), lists: z .object({ id: z.number().int().positive().optional(), name: z.string().optional(), }) .array() .optional(), }), tags: z.string().array().optional(), }) const ID_RESPONSE_SCHEMA = z.object({ id: CAMPAIGN_ID_SCHEMA }) const PROCESS_SCHEMA = z.object({ processId: z.number().int().positive() }) const REPORT_SCHEMA = z.object({ email: z.object({ body: z.string(), to: z.email().array().min(1) }), language: z.enum(["fr", "es", "pt", "it", "de", "en"]).prefault("fr"), }) /** Lists Brevo SMS campaigns with recipients and statistics. */ export const listBrevoSmsCampaigns = defineAction("List Brevo SMS campaigns") .account(BREVO_ACCOUNT) .input( z .object({ endDate: z.iso.datetime({ offset: true }).optional(), limit: z.number().int().min(1).max(1000).prefault(500), offset: z.number().int().nonnegative().prefault(0), sort: z.enum(["asc", "desc"]).prefault("desc"), startDate: z.iso.datetime({ offset: true }).optional(), status: CAMPAIGN_STATUS_SCHEMA.optional(), }) .superRefine(({ endDate, startDate, status }, ctx) => { if (Boolean(endDate) !== Boolean(startDate)) { ctx.addIssue({ code: "custom", message: "startDate and endDate must be provided together.", }) return } if (!endDate || !startDate) return const end = new Date(endDate).getTime() if (status && status !== "sent") { ctx.addIssue({ code: "custom", message: "Date filters require status sent or no status.", }) } if (end < new Date(startDate).getTime() || end > Date.now()) { ctx.addIssue({ code: "custom", message: "Use a valid, non-future date range.", }) } }), ) .output( z.object({ campaigns: SMS_CAMPAIGN_SCHEMA.array(), count: z.number().int().nonnegative(), }), ) .retry({ replaySafety: "safe" }) .handler( async ({ account, input }) => await parseBrevoResponse( await getBrevoApi(account.secret).request("smsCampaigns", { query: input, }), z.object({ campaigns: SMS_CAMPAIGN_SCHEMA.array(), count: z.number().int().nonnegative(), }), ), ) /** Creates a Brevo SMS campaign. */ export const createBrevoSmsCampaign = defineAction("Create Brevo SMS campaign") .account(BREVO_ACCOUNT) .input(CREATE_SMS_SCHEMA) .output(ID_RESPONSE_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler( async ({ account, input }) => await parseBrevoResponse( await getBrevoApi(account.secret).request("smsCampaigns", { body: JSON.stringify(input), method: "POST", }), ID_RESPONSE_SCHEMA, ), ) /** Retrieves one Brevo SMS campaign. */ export const getBrevoSmsCampaign = defineAction("Get Brevo SMS campaign") .account(BREVO_ACCOUNT) .input(z.object({ campaignId: CAMPAIGN_ID_SCHEMA })) .output(SMS_CAMPAIGN_DETAIL_SCHEMA) .retry({ replaySafety: "safe" }) .handler( async ({ account, input }) => await parseBrevoResponse( await getBrevoApi(account.secret).request( `smsCampaigns/${input.campaignId}`, ), SMS_CAMPAIGN_DETAIL_SCHEMA, ), ) /** Updates a Brevo SMS campaign. */ export const updateBrevoSmsCampaign = defineAction("Update Brevo SMS campaign") .account(BREVO_ACCOUNT) .input(UPDATE_SMS_SCHEMA) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { campaignId, ...body } = input await getBrevoApi(account.secret).request(`smsCampaigns/${campaignId}`, { body: JSON.stringify(body), method: "PUT", }) }) /** Deletes a Brevo SMS campaign. */ export const deleteBrevoSmsCampaign = defineAction("Delete Brevo SMS campaign") .account(BREVO_ACCOUNT) .input(z.object({ campaignId: CAMPAIGN_ID_SCHEMA })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getBrevoApi(account.secret).request( `smsCampaigns/${input.campaignId}`, { method: "DELETE" }, ) }) /** Starts a recipient export for a sent SMS campaign. */ export const exportBrevoSmsCampaignRecipients = defineAction( "Export Brevo SMS campaign recipients", ) .account(BREVO_ACCOUNT) .input( z.object({ campaignId: CAMPAIGN_ID_SCHEMA, notifyURL: z.url().optional(), recipientsType: z.enum([ "all", "delivered", "answered", "softBounces", "hardBounces", "unsubscribed", ]), }), ) .output(PROCESS_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { campaignId, ...body } = input return await parseBrevoResponse( await getBrevoApi(account.secret).request( `smsCampaigns/${campaignId}/exportRecipients`, { body: JSON.stringify(body), method: "POST" }, ), PROCESS_SCHEMA, ) }) /** Sends an SMS campaign immediately. */ export const sendBrevoSmsCampaignNow = defineAction( "Send Brevo SMS campaign now", ) .account(BREVO_ACCOUNT) .input(z.object({ campaignId: CAMPAIGN_ID_SCHEMA })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getBrevoApi(account.secret).request( `smsCampaigns/${input.campaignId}/sendNow`, { method: "POST" }, ) }) /** Sends an SMS campaign test to one existing contact. */ export const sendBrevoSmsCampaignTest = defineAction( "Send Brevo SMS campaign test", ) .account(BREVO_ACCOUNT) .input( z.object({ campaignId: CAMPAIGN_ID_SCHEMA, phoneNumber: z.string().regex(/^\d+$/), }), ) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getBrevoApi(account.secret).request( `smsCampaigns/${input.campaignId}/sendTest`, { body: JSON.stringify({ phoneNumber: input.phoneNumber }), method: "POST", }, ) }) /** Emails a PDF report for an SMS campaign. */ export const sendBrevoSmsCampaignReport = defineAction( "Send Brevo SMS campaign report", ) .account(BREVO_ACCOUNT) .input(REPORT_SCHEMA.extend({ campaignId: CAMPAIGN_ID_SCHEMA })) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { campaignId, ...body } = input await getBrevoApi(account.secret).request( `smsCampaigns/${campaignId}/sendReport`, { body: JSON.stringify(body), method: "POST" }, ) }) /** Changes a Brevo SMS campaign's lifecycle status. */ export const updateBrevoSmsCampaignStatus = defineAction( "Update Brevo SMS campaign status", ) .account(BREVO_ACCOUNT) .input( z.object({ campaignId: CAMPAIGN_ID_SCHEMA, status: STATUS_UPDATE_SCHEMA, }), ) .output(z.void()) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getBrevoApi(account.secret).request( `smsCampaigns/${input.campaignId}/status`, { body: JSON.stringify({ status: input.status }), method: "PUT" }, ) })