import * as z from "zod" import { defineAction } from "../../automation/actions" import { getBrevoApi, parseBrevoResponse, } from "@automate.ax/integration-contracts/brevo" const BREVO_ACCOUNT = "brevo" const PROCESS_NAME_SCHEMA = z.enum([ "IMPORTUSER", "SEARCH_EXPORT_USERS", "TRANS-CALC", "TRANS-GLOBAL-CALC", "CAMPAIGN_PROCESSING", "LIST_EXPORT", "CONTACT_EXPORT", ]) const PROCESS_STATUS_SCHEMA = z.enum([ "queued", "in_process", "processing", "completed", "failed", "cancelled", ]) const PROCESS_IMPORT_INFO_WIRE_SCHEMA = z.object({ duplicate_contact_id: z.url().nullable().optional(), duplicate_email_id: z.url().nullable().optional(), duplicate_ext_id: z.url().nullable().optional(), duplicate_landline_number_id: z.url().nullable().optional(), duplicate_phone_id: z.url().nullable().optional(), duplicate_whatsapp_id: z.url().nullable().optional(), invalid_emails: z.url().nullable().optional(), }) const PROCESS_WIRE_SCHEMA = z.object({ export_url: z.url().optional(), id: z.number().int().positive(), info: z .object({ import: PROCESS_IMPORT_INFO_WIRE_SCHEMA.optional() }) .optional(), name: PROCESS_NAME_SCHEMA, status: PROCESS_STATUS_SCHEMA, }) const PROCESS_SCHEMA = z.object({ /** Download URL produced by an export process. */ exportUrl: z.url().optional(), /** Numeric Brevo process ID. */ id: z.number().int().positive(), /** Import diagnostics produced after completion. */ import: z .object({ duplicateContactId: z.url().nullable().optional(), duplicateEmailId: z.url().nullable().optional(), duplicateExternalId: z.url().nullable().optional(), duplicateLandlineNumberId: z.url().nullable().optional(), duplicatePhoneId: z.url().nullable().optional(), duplicateWhatsappId: z.url().nullable().optional(), invalidEmails: z.url().nullable().optional(), }) .optional(), /** Provider process category. */ name: PROCESS_NAME_SCHEMA, /** Current provider process state. */ status: PROCESS_STATUS_SCHEMA, }) const ACCOUNT_WIRE_SCHEMA = z.object({ address: z .object({ city: z.string(), country: z.string(), street: z.string(), zipCode: z.string(), }) .optional(), companyName: z.string(), email: z.email(), enterprise: z.boolean(), firstName: z.string(), lastName: z.string(), marketingAutomation: z .object({ enabled: z.boolean(), key: z.string().optional() }) .optional(), organization_id: z.string().min(1), plan: z .object({ credits: z.number(), creditsType: z.literal("sendLimit"), endDate: z.string().optional(), startDate: z.string().optional(), type: z.enum(["payAsYouGo", "free", "subscription", "sms"]), }) .array(), planVerticals: z .object({ credits: z.string().optional(), endDate: z.string().optional(), name: z.string(), planCategory: z.enum(["Marketing", "Chat", "CRM"]), planType: z.string(), startDate: z.string().optional(), status: z.enum(["active", "inactive"]), users: z .object({ purchasedSeats: z.string(), usedSeats: z.string() }) .nullable() .optional(), }) .array() .optional(), relay: z.object({ data: z.object({ port: z.number().int().positive(), relay: z.string(), userName: z.string(), }), enabled: z.boolean(), }), user_id: z.number().int().positive(), }) const ACCOUNT_SCHEMA = ACCOUNT_WIRE_SCHEMA.omit({ organization_id: true, user_id: true, }).extend({ /** Brevo organization ID. */ organizationId: z.string().min(1), /** Brevo user ID associated with the API key. */ userId: z.number().int().positive(), }) const ACCOUNT_ACTIVITY_WIRE_SCHEMA = z.object({ logs: z .object({ action: z.string(), date: z.string(), user_agent: z.string(), user_email: z.email(), user_ip: z.string(), }) .array() .optional(), }) const ACCOUNT_ACTIVITY_SCHEMA = z.object({ logs: z .object({ action: z.string(), date: z.string(), userAgent: z.string(), userEmail: z.email(), userIp: z.string(), }) .array(), }) /** Retrieves the Brevo account, plan, credit, and SMTP relay summary. */ export const getBrevoAccount = defineAction("Get Brevo account") .describe("Retrieves the connected Brevo account and plan summary.") .account(BREVO_ACCOUNT) .output(ACCOUNT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account }) => { const { organization_id, user_id, ...rest } = await parseBrevoResponse( await getBrevoApi(account.secret).request("account"), ACCOUNT_WIRE_SCHEMA, ) return { ...rest, organizationId: organization_id, userId: user_id } }) /** Lists Brevo account activity with optional date and user filters. */ export const listBrevoAccountActivity = defineAction( "List Brevo account activity", ) .describe("Lists security and configuration activity in a Brevo account.") .account(BREVO_ACCOUNT) .input( z .object({ endDate: z.iso.date().optional(), limit: z.number().int().min(1).max(500).prefault(50), offset: z.number().int().nonnegative().prefault(0), startDate: z.iso.date().optional(), userEmail: z.email().optional(), }) .refine( ({ endDate, startDate }) => Boolean(endDate) === Boolean(startDate), { message: "startDate and endDate must be provided together." }, ) .superRefine(({ endDate, startDate }, context) => { if (!endDate || !startDate) return const start = Date.parse(`${startDate}T00:00:00Z`) const end = Date.parse(`${endDate}T00:00:00Z`) if (end < start) { context.addIssue({ code: "custom", message: "endDate must be on or after startDate.", }) } if (end - start > 31 * 24 * 60 * 60 * 1_000) { context.addIssue({ code: "custom", message: "Account activity date ranges cannot exceed one month.", }) } if (start < Date.now() - 366 * 24 * 60 * 60 * 1_000) { context.addIssue({ code: "custom", message: "Account activity is available for the previous 12 months.", }) } }), ) .output(ACCOUNT_ACTIVITY_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const { logs } = await parseBrevoResponse( await getBrevoApi(account.secret).request("organization/activities", { query: { email: input.userEmail, endDate: input.endDate, limit: input.limit, offset: input.offset, startDate: input.startDate, }, }), ACCOUNT_ACTIVITY_WIRE_SCHEMA, ) return { logs: (logs ?? []).map(({ user_agent, user_email, user_ip, ...log }) => ({ ...log, userAgent: user_agent, userEmail: user_email, userIp: user_ip, })), } }) /** Lists asynchronous Brevo import, export, and campaign processes. */ export const listBrevoProcesses = defineAction("List Brevo processes") .describe("Lists asynchronous imports, exports, and campaign processes.") .account(BREVO_ACCOUNT) .input( z.object({ limit: z.number().int().min(1).max(50).prefault(50), offset: z.number().int().nonnegative().prefault(0), sort: z.enum(["asc", "desc"]).prefault("desc"), }), ) .output( z.object({ count: z.number().int().nonnegative(), processes: PROCESS_SCHEMA.array(), }), ) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => { const result = await parseBrevoResponse( await getBrevoApi(account.secret).request("processes", { query: input }), z.object({ count: z.number().int().nonnegative(), processes: PROCESS_WIRE_SCHEMA.array(), }), ) return { count: result.count, processes: result.processes.map(toProcess) } }) /** Retrieves one asynchronous Brevo process. */ export const getBrevoProcess = defineAction("Get Brevo process") .describe("Retrieves one import, export, or campaign process.") .account(BREVO_ACCOUNT) .input(z.object({ processId: z.number().int().positive() })) .output(PROCESS_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => toProcess( await parseBrevoResponse( await getBrevoApi(account.secret).request( `processes/${input.processId}`, ), PROCESS_WIRE_SCHEMA, ), ), ) /** * Maps provider-native process fields to the public contract. * * @param process - Provider process response. */ function toProcess(process: z.output) { const { export_url, info, ...rest } = process const imported = info?.import return PROCESS_SCHEMA.parse({ ...rest, exportUrl: export_url, import: imported ? { duplicateContactId: imported.duplicate_contact_id, duplicateEmailId: imported.duplicate_email_id, duplicateExternalId: imported.duplicate_ext_id, duplicateLandlineNumberId: imported.duplicate_landline_number_id, duplicatePhoneId: imported.duplicate_phone_id, duplicateWhatsappId: imported.duplicate_whatsapp_id, invalidEmails: imported.invalid_emails, } : undefined, }) }