/** * Actions are individual steps that you add to an integration * built with the low-code designer. Each step receives a set * of inputs and connection information for a third-party API, * does work, and returns a result. * * For information on actions, see * https://prismatic.io/docs/custom-connectors/actions/ */ import { action, input, util } from "@prismatic-io/spectral"; import { inputs as httpClientInputs, sendRawRequest, createClient, } from "@prismatic-io/spectral/dist/clients/http"; import { createCakemailClient, buildQueryParams } from "./client"; export const connectionInput = input({ label: "Cakemail Connection", required: true, type: "connection", }); export const listIdInput = input({ label: "List ID", type: "string", required: true, dataSource: "selectCakemailList", comments: "The ID of the list", clean: util.types.toString, }); /* Get a specific item from inventory by ID */ const getContactsOfAList = action({ display: { label: "Get Contacts of a list", description: "Get Contacts of a list", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), list_id: input({ label: "list_id", type: "string" }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page", }), with_count: input({ label: "With count", type: "string", required: false, comments: "nclude count in the response", }), }, perform: async ( context, { cakemailConnection, account_id, list_id, page, per_page, with_count } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), }); const response = await cakemailClient.get( `/lists/${list_id}/contacts${queryParams}` ); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 1, email: "test@test.com", status: "active", subscribed_on: 1746820256, last_bounce_type: "none", bounces_count: 0, custom_attributes: [], tags: [], interests: [], }, ], pagination: { page: 1, per_page: 10, count: 1, }, }, }, }); /* Get webhooks with optional query parameters */ const getWebhooks = action({ display: { label: "Get Webhooks", description: "Get all configured webhooks with optional filtering", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), // Optional query parameters page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page", }), with_count: input({ label: "With count", type: "string", required: false, comments: "nclude count in the response", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page, with_count } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), }); const response = await cakemailClient.get(`/webhooks${queryParams}`); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 123, url: "https://example.com/webhook", event: "contact.created", status: "active", rate_limit: 100, rate_limit_period: "second", created_on: 1704067200, }, ], pagination: { page: 1, per_page: 50, count: 1, }, }, }, }); const rawRequest = action({ display: { label: "Raw Request", description: "Send an HTTP request to any Cakemail endpoint", }, inputs: { cakemailConnection: connectionInput, ...httpClientInputs, url: { // Optional; this adds component-specific instructions to the URL input ...httpClientInputs.url, comments: "The base URL from your connection is already included (https://api.cakemail.dev). For example, to connect to https://api.cakemail.dev/contacts.", example: "/contacts", }, }, perform: async (context, { cakemailConnection, ...httpClientInputs }) => { const { data } = await sendRawRequest( util.types.toString("https://api.cakemail.dev"), httpClientInputs, { Authorization: `Bearer ${cakemailConnection.fields.apiKey}` } // Authorization methods vary by API ); return { data }; }, }); /* Add a new contact to a list */ const addContact = action({ display: { label: "Add Contact", description: "Add a new contact to a list", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), list_id: input({ label: "List ID", type: "string", required: true }), email: input({ label: "Email", type: "string", required: true }), resubscribe: input({ label: "Resubscribe", type: "boolean", required: false, comments: "Whether to resubscribe unsubscribed contacts", }), custom_attributes: input({ label: "Custom Attributes", type: "code", language: "json", required: false, comments: "JSON object of custom attributes. Use the Expression Editor to reference step outputs dynamically", example: '[{"name": "Cakemail", "value": "developer"}]', }), }, perform: async ( context, { cakemailConnection, account_id, list_id, email, resubscribe, custom_attributes, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const contactData: any = { email, }; if (custom_attributes) { try { contactData.custom_attributes = typeof custom_attributes === "string" ? JSON.parse(custom_attributes) : custom_attributes; } catch (error) { throw new Error( `Invalid JSON format for custom_attributes: ${custom_attributes}. Error: ${error instanceof Error ? error.message : "Unknown error"}` ); } } const queryParams = buildQueryParams({ account_id: finalAccountId, ...(resubscribe ? { resubscribe } : {}), }); try { const response = await cakemailClient.post( `/lists/${list_id}/contacts${queryParams}`, contactData ); return { data: response.data }; } catch (error) { throw error; } }, examplePayload: { data: { id: 123, email: "john.doe@example.com", status: "active", custom_attributes: [ { name: "company", value: "Cakemail", }, { name: "role", value: "developer", }, ], tags: [], interests: [], subscribed_on: 1746820256, last_bounce_type: "none", bounces_count: 0, }, }, }); /* Delete a contact from a list */ const deleteContact = action({ display: { label: "Delete Contact", description: "Delete a contact from a list", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), //list_id: input({ label: "list_id", type: "string" }), list_id: { ...listIdInput, required: false }, contact_id: input({ label: "contact_id", type: "string" }), }, perform: async ( context, { cakemailConnection, account_id, list_id, contact_id } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.delete( `/lists/${list_id}/contacts/${contact_id}${queryParams}` ); return { data: response.data }; }, examplePayload: { data: null, }, }); const getTemplates = action({ display: { label: "Get Templates", description: "Get all templates for an account with optional filtering and pagination", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, comments: "The ID of the account to retrieve templates from", }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page (max 100)", }), with_count: input({ label: "With Count", type: "string", required: false, comments: "Include count in the response (true/false)", }), filter: input({ label: "Filter", type: "string", required: false, comments: "Filter templates using valid terms (tag, not_tag, name, is_owner, is_not_owner) and operators (==, !=, >, <, >=, <=). Use ; as separator", }), sort: input({ label: "Sort", type: "string", required: false, comments: "Sort templates by field and direction using syntax [-|+]field. Valid fields: id, created_on, updated_on, name", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page, with_count, filter, sort } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), ...(filter ? { filter } : {}), ...(sort ? { sort } : {}), }); const response = await cakemailClient.get(`/templates${queryParams}`); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 943261, name: "Template Name", description: "", created_on: 1754917869, updated_on: 1754917869, tags: ["bee"], is_owner: true, sharing: { shared: false, }, }, ], pagination: { page: 1, per_page: 25, count: 232, }, }, }, }); const submitEmail = action({ display: { label: "Submit Email", description: "Submit an email using the Cakemail Email API", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, comments: "The ID of the account", }), list_id: input({ label: "List ID", type: "string", required: true, comments: "The ID of the list to send the email to", }), sender_id: input({ label: "Sender ID", type: "string", required: true, comments: "The ID of the sender. You can find this in the Senders section of the dashboard.", }), email: input({ label: "Recipient Email", type: "string", required: true, comments: "The email address of the recipient", }), subject: input({ label: "Subject", type: "string", required: true, comments: "The subject line of the email", }), html_content: input({ label: "HTML Content", type: "code", language: "html", required: false, comments: "The HTML content of the email", }), text_content: input({ label: "Text Content", type: "code", language: "plaintext", required: false, comments: "The plain text content of the email (optional)", }), content_type: input({ label: "Content Type", type: "string", required: false, default: "marketing", comments: "The type of email content (marketing, transactional, etc.)", }), encoding: input({ label: "Encoding", type: "string", required: false, default: "utf-8", comments: "The character encoding of the email content", }), template_id: input({ label: "Template ID", type: "string", required: true, comments: "The ID of the template to use for the email (will be nested in content.template.id)", }), custom_attributes: input({ label: "Custom Attributes", type: "code", language: "json", required: false, comments: "Array of custom attributes for the email", example: '[{"name": "campaign_id", "value": "123", "type": "string"}]', }), }, perform: async ( context, { cakemailConnection, account_id, list_id, sender_id, email, subject, html_content, text_content, content_type, encoding, template_id, custom_attributes, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build content object according to API specification const contentData: any = { subject, type: content_type || "marketing", }; // Add HTML content if provided if (html_content) { contentData.html = html_content; } // Add text content if provided if (text_content) { contentData.text = text_content; } // Add template if provided if (template_id && typeof template_id === "string") { contentData.template = { id: parseInt(template_id, 10), }; } // Add encoding if provided if (encoding) { contentData.encoding = encoding; } // Add custom attributes if provided if (custom_attributes) { try { contentData.custom_attributes = typeof custom_attributes === "string" ? JSON.parse(custom_attributes) : custom_attributes; } catch (error) { throw new Error( `Invalid JSON format for custom_attributes: ${custom_attributes}. Error: ${error instanceof Error ? error.message : "Unknown error"}` ); } } const emailData: any = { list_id, sender: { id: sender_id, }, email, content: contentData, }; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.post( `/v2/emails${queryParams}`, emailData ); return { data: response.data }; }, examplePayload: { data: { id: 12345, submitted_on: 1704067200, status: "queued", }, }, }); /* Update a contact in a list */ const updateContact = action({ display: { label: "Update Contact", description: "Update an existing contact in a list using PATCH endpoint", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, }), list_id: input({ label: "List ID", type: "string", required: true }), contact_id: input({ label: "Contact ID", type: "string", required: true }), email: input({ label: "Email", type: "string", required: false }), custom_attributes: input({ label: "Custom Attributes (JSON)", type: "code", language: "json", required: false, comments: "JSON array of custom attribute objects. Use the Expression Editor to reference step outputs dynamically", example: '[{"name": "company", "value": "Cakemail"}, {"name": "role", "value": "developer"}]', }), custom_attributes_keyvalues: input({ label: "Custom Attributes (Key-Value)", type: "string", collection: "keyvaluelist", required: false, comments: "Add custom attributes using key-value pairs. Will be combined with JSON custom attributes if both are provided", }), tags: input({ label: "Tags", type: "code", language: "json", required: false, comments: "Array of tag IDs to assign to the contact", example: "[1, 2, 3]", }), interests: input({ label: "Interests", type: "code", language: "json", required: false, comments: "Array of interest IDs to assign to the contact", example: "[1, 2, 3]", }), }, perform: async ( context, { cakemailConnection, account_id, list_id, contact_id, email, custom_attributes, custom_attributes_keyvalues, tags, interests, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const contactData: any = {}; if (email) contactData.email = email; // Handle custom attributes - combine both JSON and key-value inputs if (custom_attributes || custom_attributes_keyvalues) { let attributesArray: any[] = []; // Parse JSON custom attributes if provided if (custom_attributes) { try { const parsedAttributes = typeof custom_attributes === "string" ? JSON.parse(custom_attributes) : custom_attributes; // Ensure it's an array if (Array.isArray(parsedAttributes)) { attributesArray = [...parsedAttributes]; } else { throw new Error("custom_attributes must be an array of objects"); } } catch (error) { throw new Error( `Invalid JSON format for custom_attributes: ${custom_attributes}. Error: ${error instanceof Error ? error.message : "Unknown error"}` ); } } // Add key-value custom attributes if provided if ( custom_attributes_keyvalues && custom_attributes_keyvalues.length > 0 ) { const keyValueAttributes = custom_attributes_keyvalues.map( (pair: any) => ({ name: pair.key, value: pair.value, }) ); attributesArray = [...attributesArray, ...keyValueAttributes]; } // Only set custom_attributes if we have at least one attribute if (attributesArray.length > 0) { contactData.custom_attributes = attributesArray; } } if (tags) { try { contactData.tags = typeof tags === "string" ? JSON.parse(tags) : tags; } catch (error) { throw new Error("Invalid JSON format for tags"); } } if (interests) { try { contactData.interests = typeof interests === "string" ? JSON.parse(interests) : interests; } catch (error) { throw new Error("Invalid JSON format for interests"); } } const queryParams = buildQueryParams({ account_id: finalAccountId }); try { const response = await cakemailClient.patch( `/lists/${list_id}/contacts/${contact_id}${queryParams}`, contactData ); return { data: response.data }; } catch (error) { throw error; } }, examplePayload: { data: { id: 123, email: "john.doe@example.com", status: "active", custom_attributes: [ { name: "company", value: "Cakemail", }, { name: "role", value: "developer", }, ], tags: [1, 2], interests: [1], subscribed_on: 1746820256, last_bounce_type: "none", bounces_count: 0, }, }, }); /* Custom Attributes Actions */ const getCustomAttributes = action({ display: { label: "Get Custom Attributes", description: "Get all custom attributes for an account", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), list_id: input({ label: "list_id", type: "string" }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page, list_id } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), }); const response = await cakemailClient.get( `/lists/${list_id}/custom-attributes${queryParams}` ); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 1, name: "company", type: "string", created_on: 1704067200, }, ], pagination: { page: 1, per_page: 50, count: 1, }, }, }, }); const createCustomAttribute = action({ display: { label: "Create Custom Attribute", description: "Create a new custom attribute", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), list_id: input({ label: "list_id", type: "string" }), name: input({ label: "name", type: "string" }), type: input({ label: "type", type: "string", comments: "Attribute type: string, number, date, boolean", }), }, perform: async ( context, { cakemailConnection, account_id, name, type, list_id } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.post( `/lists/${list_id}/custom-attributes${queryParams}`, { name, type } ); return { data: response.data }; }, examplePayload: { data: { id: 1, name: "company", type: "string", created_on: 1704067200, }, }, }); const deleteCustomAttribute = action({ display: { label: "Delete Custom Attribute", description: "Delete a custom attribute", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), attribute_id: input({ label: "attribute_id", type: "string" }), }, perform: async ( context, { cakemailConnection, account_id, attribute_id } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.delete( `/custom-attributes/${attribute_id}${queryParams}` ); return { data: response.data }; }, examplePayload: { data: null, }, }); /* Tags Actions */ const getTags = action({ display: { label: "Get Tags", description: "Get all tags for an account", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), }); const response = await cakemailClient.get(`/tags${queryParams}`); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 1, name: "vip", color: "#FF0000", created_on: 1704067200, }, ], pagination: { page: 1, per_page: 50, count: 1, }, }, }, }); const createTag = action({ display: { label: "Create Tag", description: "Create a new tag", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), name: input({ label: "name", type: "string" }), color: input({ label: "color", type: "string", required: false, comments: "Tag color in hex format (e.g., #FF0000)", }), }, perform: async (context, { cakemailConnection, account_id, name, color }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const tagData: any = { name }; if (color) tagData.color = color; const response = await cakemailClient.post( `/tags${queryParams}`, tagData ); return { data: response.data }; }, examplePayload: { data: { id: 1, name: "vip", color: "#FF0000", created_on: 1704067200, }, }, }); const deleteTag = action({ display: { label: "Delete Tag", description: "Delete a tag", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), tag_id: input({ label: "tag_id", type: "string" }), }, perform: async (context, { cakemailConnection, account_id, tag_id }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.delete( `/tags/${tag_id}${queryParams}` ); return { data: response.data }; }, examplePayload: { data: null, }, }); /* Interests Actions */ const getInterests = action({ display: { label: "Get Interests", description: "Get all interests for an account", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account Id (optional)", type: "string", required: false, }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), }); const response = await cakemailClient.get(`/interests${queryParams}`); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 1, name: "newsletter", created_on: 1704067200, }, ], pagination: { page: 1, per_page: 50, count: 1, }, }, }, }); const createInterest = action({ display: { label: "Create Interest", description: "Create a new interest", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "account_id", type: "string", required: false }), name: input({ label: "name", type: "string" }), }, perform: async (context, { cakemailConnection, account_id, name }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.post( `/interests${queryParams}`, { name } ); return { data: response.data }; }, examplePayload: { data: { id: 1, name: "newsletter", created_on: 1704067200, }, }, }); const deleteInterest = action({ display: { label: "Delete Interest", description: "Delete an interest", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "account_id", type: "string", required: false }), interest_id: input({ label: "interest_id", type: "string" }), }, perform: async (context, { cakemailConnection, account_id, interest_id }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.delete( `/interests/${interest_id}${queryParams}` ); return { data: response.data }; }, examplePayload: { data: null, }, }); /* Webhook Management Actions */ const createWebhook = action({ display: { label: "Create Webhook", description: "Create a new webhook", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, }), url: input({ label: "url", type: "string" }), rate_limit: input({ label: "rate_limit", type: "string" }), rate_limit_period: input({ label: "rate_limit_period", type: "string" }), event: input({ label: "event", type: "string", comments: "Comma-separated list of events (e.g., contact.created,contact.updated)", }), }, perform: async ( context, { cakemailConnection, account_id, url, event, rate_limit, rate_limit_period, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const webhookData: any = { url, event, rate_limit, rate_limit_period }; const response = await cakemailClient.post( `/webhooks${queryParams}`, webhookData ); return { data: response.data }; }, examplePayload: { data: { id: 123, url: "https://example.com/webhook", event: "contact.created", status: "active", rate_limit: 100, rate_limit_period: "second", created_on: 1704067200, }, }, }); const updateWebhook = action({ display: { label: "Update Webhook", description: "Update an existing webhook", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, }), webhook_id: input({ label: "webhook_id", type: "string" }), url: input({ label: "url", type: "string", required: false, }), events: input({ label: "events", type: "string", required: false, comments: "Comma-separated list of events", }), status: input({ label: "status", type: "string", required: false, comments: "Webhook status: active, inactive", }), }, perform: async ( context, { cakemailConnection, account_id, webhook_id, url, events, status } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const webhookData: any = {}; if (url) webhookData.url = url; if (events) webhookData.events = events; if (status) webhookData.status = status; const response = await cakemailClient.put( `/webhooks/${webhook_id}${queryParams}`, webhookData ); return { data: response.data }; }, examplePayload: { data: { id: 123, url: "https://example.com/webhook", event: "contact.updated", status: "active", rate_limit: 100, rate_limit_period: "second", created_on: 1704067200, }, }, }); const deleteWebhook = action({ display: { label: "Archive Webhook", description: "Archive a webhook", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, }), webhook_id: input({ label: "webhook_id", type: "string" }), }, perform: async (context, { cakemailConnection, account_id, webhook_id }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.post( `/webhooks/${webhook_id}/archive${queryParams}` ); return { data: response.data }; }, examplePayload: { data: null, }, }); /* Import Contacts Action */ const importContacts = action({ display: { label: "Import Contacts", description: "Import multiple contacts to a list", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, }), list_id: input({ label: "list_id", type: "string" }), contacts: input({ label: "Contacts", type: "code", language: "json", required: true, comments: "Array of contact objects with email and optional fields custom_attributes, tags, interests", example: '[{"email":"john@example.com","custom_attributes":{"company":"Cakemail"}}]', }), resubscribe: input({ label: "Resubscribe", type: "string", required: false, comments: "Whether to resubscribe unsubscribed contacts", }), }, perform: async ( context, { cakemailConnection, account_id, list_id, contacts, resubscribe } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Parse contacts if it's a string, otherwise use as is let contactsArray; if (typeof contacts === "string") { try { contactsArray = JSON.parse(contacts); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new Error(`Invalid JSON format for contacts: ${errorMessage}`); } } else { contactsArray = contacts; } console.log(contactsArray); const queryParams = buildQueryParams({ account_id: finalAccountId, }); try { const response = await cakemailClient.post( `/lists/${list_id}/import-contacts${queryParams}`, { contacts: contactsArray, ...(resubscribe ? { resubscribe } : {}), } ); return { data: response.data }; } catch (error) { throw error; } }, examplePayload: { data: { id: 12345, status: "processing", created_on: 1704067200, }, }, }); /* Get Current Account Action */ const getCurrentAccount = action({ display: { label: "Get Current Account", description: "Get information about the currently authenticated account", }, inputs: { cakemailConnection: connectionInput, }, perform: async (context, { cakemailConnection }) => { const cakemailClient = createCakemailClient(cakemailConnection); const response = await cakemailClient.get("/accounts/self"); return { data: response.data }; }, examplePayload: { data: { id: 123, name: "My Company", email: "admin@mycompany.com", status: "active", created_on: 1704067200, primary_color: "#FF0000", secondary_color: "#00FF00", locale: "en_US", timezone: "America/New_York", }, }, }); /* List Campaigns Action */ const listCampaigns = action({ display: { label: "List Campaigns", description: "Get all campaigns for an account with optional filtering and pagination", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID", type: "string", required: false, comments: "The ID of the account to retrieve campaigns from", }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page (max 100)", }), with_count: input({ label: "With Count", type: "string", required: false, comments: "Include count in the response (true/false)", }), list_id: input({ label: "List ID", type: "string", required: false, comments: "Filter campaigns by specific list ID", }), sort: input({ label: "Sort", type: "string", required: false, comments: "Sort campaigns by field and direction using syntax [-|+]field. Valid fields: id, name, status, type, created_at, updated_at, scheduled_at, sent_at", example: "name, created_on,scheduled_for,scheduled_on,updated_on,type", }), filter: input({ label: "Filter", type: "string", required: false, comments: "Filter campaigns using valid terms (status, name, type, list_id) and operators (==, !=, >, <, >=, <=). Use ; as separator", example: "status==sent;type==regular;list_id==123", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page, with_count, list_id, sort, filter, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), ...(list_id ? { list_id } : {}), ...(sort ? { sort } : {}), ...(filter ? { filter } : {}), }); const response = await cakemailClient.get(`/campaigns${queryParams}`); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 123456, name: "Welcome Email Campaign", type: "standard", status: "sent", created_on: 1704067200, scheduled_on: 1704153600, scheduled_for: 1704153600, next_step: null, }, ], pagination: { page: 1, per_page: 25, count: 1, }, }, }, }); /* Get Account Action */ const getAccount = action({ display: { label: "Get Account", description: "Get information about a specific account by ID", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account to retrieve", }), }, perform: async (context, { cakemailConnection, account_id }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const response = await cakemailClient.get( `/accounts/${finalAccountId}` ); return { data: response.data }; }, examplePayload: { data: { id: 123, name: "Example Company", email: "admin@example.com", status: "active", created_on: 1704067200, primary_color: "#FF0000", secondary_color: "#00FF00", locale: "en_US", timezone: "America/New_York", }, }, }); /* List Users Action */ const listUsers = action({ display: { label: "List Users", description: "Get all users for an account with optional filtering and pagination", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account to retrieve users from", }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page (max 100)", }), with_count: input({ label: "With Count", type: "string", required: false, comments: "Include count in the response (true/false)", }), filter: input({ label: "Filter", type: "string", required: false, comments: "Filter users using valid terms (email, first_name, last_name, status) and operators (==, !=, >, <, >=, <=). Use ; as separator", example: "status==active;email==user@example.com", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page, with_count, filter } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), ...(filter ? { filter } : {}), }); const response = await cakemailClient.get(`/users${queryParams}`); return { data: response.data }; }, examplePayload: { data: { pagination: { page: 1, per_page: 25, count: 2, }, data: [ { id: "user_456", email: "user@example.com", status: "active", first_name: "John", last_name: "Doe", last_activity_on: 1704067200, created_on: 1704067200, expires_on: 1735689600, }, { id: "user_789", email: "manager@example.com", status: "active", first_name: "Jane", last_name: "Smith", last_activity_on: 1704063600, created_on: 1704063600, expires_on: 1735686000, }, ], }, }, }); /* Get Automations Action */ const getAutomations = action({ display: { label: "Get Automations", description: "Get all automations for an account with optional filtering and pagination", }, inputs: { cakemailConnection: connectionInput, accountId: input({ label: "Account ID", type: "string", required: false, comments: "The ID of the account to retrieve automations from", }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page (max 100)", }), with_count: input({ label: "With Count", type: "string", required: false, comments: "Include count in the response (true/false)", }), filter: input({ label: "Filter", type: "string", required: false, comments: "Filter automations using valid terms and operators. Use ; as separator", example: "status==active", }), }, perform: async ( context, { cakemailConnection, accountId, page, per_page, with_count, filter } ) => { const cakemailClient = createCakemailClient( cakemailConnection, "https://us-central1-automation-prd-79f43.cloudfunctions.net" ); // Priority: input accountId, fallback to connection account_id const finalAccountId = accountId || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ accountId: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), ...(filter ? { filter } : {}), }); const response = await cakemailClient.get( `/automations${queryParams}` ); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 123456, name: "Welcome Email Automation", status: "active", created_on: 1704067200, updated_on: 1704067200, }, ], pagination: { page: 1, per_page: 25, count: 1, }, }, }, }); /* Get Account Statistics Action */ const getAccountStats = action({ display: { label: "Get Account Statistics", description: "Show account report with statistics and metrics for a specific account", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account to get statistics for (integer >= 1)", }), start_time: input({ label: "Start Time", type: "string", required: false, comments: "Unix timestamp for start time (integer between 1 and 2147483647)", }), end_time: input({ label: "End Time", type: "string", required: false, comments: "Unix timestamp for end time (integer between 1 and 2147483647)", }), }, perform: async ( context, { cakemailConnection, account_id, start_time, end_time } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters const queryParams = buildQueryParams({ ...(start_time ? { start_time } : {}), ...(end_time ? { end_time } : {}), }); const response = await cakemailClient.get( `/reports/accounts/${finalAccountId}${queryParams}` ); return { data: response.data }; }, examplePayload: { data: { active_contacts: 15420, sent_emails: 125000, sent_campaign_emails: 100000, sent_action_emails: 15000, sent_email_emails: 10000, open_rate: 30.5, bounce_rate: 2.1, click_rate: 7.2, spam_rate: 0.1, unsubscribe_rate: 1.0, emails_usage: 125000, contacts_usage: 15420, bounces_hard: 2500, bounce_hard_rate: 2.0, spams: 125, current_lists: 15, email_api: { submitted: 10000, delivered: 9800, }, }, }, }); /* Get Lists Action (alias: listLists) */ const getLists = action({ display: { label: "Get Lists", description: "Get all lists for an account with optional filtering and pagination", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account to retrieve lists from", }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page (max 100)", }), with_count: input({ label: "With Count", type: "string", required: false, comments: "Include count in the response (true/false)", }), filter: input({ label: "Filter", type: "string", required: false, comments: "Filter lists using valid terms (name, status) and operators (==, !=, >, <, >=, <=). Use ; as separator", }), sort: input({ label: "Sort", type: "string", required: false, comments: "Sort lists by field and direction using syntax [-|+]field. Valid fields: id, name, created_at, updated_at", }), }, perform: async ( context, { cakemailConnection, account_id, page, per_page, with_count, filter, sort } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), ...(filter ? { filter } : {}), ...(sort ? { sort } : {}), }); const response = await cakemailClient.get(`/lists${queryParams}`); return { data: response.data }; }, examplePayload: { data: { data: [ { id: 123, name: "Newsletter Subscribers", status: "active", language: "en_US", created_on: 1704067200, updated_on: 1704067200, }, { id: 456, name: "VIP Customers", status: "active", language: "en_US", created_on: 1707745200, updated_on: 1707745200, }, ], pagination: { page: 1, per_page: 25, count: 2, }, }, }, }); // Alias for getLists to match API endpoint naming const listLists = getLists; /* Create List Action */ const createList = action({ display: { label: "Create List", description: "Create a new email list", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account to create the list in", }), name: input({ label: "List Name", type: "string", required: true, comments: "The name of the list", }), description: input({ label: "Description", type: "string", required: false, comments: "Optional description for the list", }), status: input({ label: "Status", type: "string", required: false, default: "active", comments: "List status: active, inactive", }), double_opt_in: input({ label: "Double Opt-in", type: "boolean", required: false, comments: "Whether to require double opt-in for new subscribers", }), allow_duplicate_emails: input({ label: "Allow Duplicate Emails", type: "boolean", required: false, comments: "Whether to allow duplicate email addresses in the list", }), }, perform: async ( context, { cakemailConnection, account_id, name, description, status, double_opt_in, allow_duplicate_emails, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const listData: any = { name, ...(description ? { description } : {}), ...(status ? { status } : {}), ...(double_opt_in !== undefined ? { double_opt_in } : {}), ...(allow_duplicate_emails !== undefined ? { allow_duplicate_emails } : {}), }; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.post( `/lists${queryParams}`, listData ); return { data: response.data }; }, examplePayload: { data: { id: 789, name: "New Newsletter List", status: "active", language: "en_US", created_on: 1704067200, updated_on: 1704067200, }, }, }); const getUser = action({ display: { label: "Get User", description: "Get information about a specific user by ID", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account that contains the user", }), user_id: input({ label: "User ID", type: "string", required: true, comments: "The ID of the user to retrieve", }), }, perform: async (context, { cakemailConnection, account_id, user_id }) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.get( `/users/${user_id}${queryParams}` ); return { data: response.data }; }, examplePayload: { data: { id: 456, email: "user@example.com", status: "active", first_name: "John", last_name: "Doe", last_activity_on: 1704067200, created_on: 1704067200, expires_on: 1735689600, }, }, }); /* Unsubscribe a contact from a list */ const unsubscribeContact = action({ display: { label: "Unsubscribe Contact", description: "Unsubscribe a contact from a list", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, comments: "The ID of the account", }), list_id: input({ label: "List ID", type: "string", required: true, comments: "The ID of the list to unsubscribe the contact from", }), contact_id: input({ label: "Contact ID", type: "string", required: true, comments: "The ID of the contact to unsubscribe", }), }, perform: async ( context, { cakemailConnection, account_id, list_id, contact_id } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; const queryParams = buildQueryParams({ account_id: finalAccountId }); const response = await cakemailClient.post( `/lists/${list_id}/contacts/${contact_id}/unsubscribe${queryParams}` ); return { data: response.data }; }, examplePayload: { data: { id: 123, email: "user@example.com", status: "unsubscribed", subscribed_on: 1704067200, unsubscribed_on: 1704153600, last_bounce_type: "none", bounces_count: 0, custom_attributes: [], tags: [], interests: [], }, }, }); /* Tag Multiple Contacts */ const tagMultipleContacts = action({ display: { label: "Tag Multiple Contacts", description: "Assign tags to multiple contacts in a list simultaneously", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID", type: "string", required: false, comments: "The ID of the account", }), list_id: input({ label: "List ID", type: "string", required: true, comments: "The ID of the list containing the contacts", }), contact_ids: input({ label: "Contact IDs", type: "code", language: "json", required: true, comments: "Array of contact IDs to tag", example: '["123", "456", "789"]', }), tags: input({ label: "Tags", type: "code", language: "json", required: true, comments: "Array of tag IDs to assign to the contacts", example: "[1, 2, 3]", }), }, perform: async ( context, { cakemailConnection, account_id, list_id, contact_ids, tags } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Parse contact_ids if it's a string let contactIdsArray; if (typeof contact_ids === "string") { try { contactIdsArray = JSON.parse(contact_ids); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new Error(`Invalid JSON format for contact_ids: ${errorMessage}`); } } else { contactIdsArray = contact_ids; } // Parse tags if it's a string let tagsArray; if (typeof tags === "string") { try { tagsArray = JSON.parse(tags); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new Error(`Invalid JSON format for tags: ${errorMessage}`); } } else { tagsArray = tags; } const queryParams = buildQueryParams({ account_id: finalAccountId }); try { const response = await cakemailClient.post( `/lists/${list_id}/contacts/tag${queryParams}`, { contact_ids: contactIdsArray, tags: tagsArray, } ); return { data: response.data }; } catch (error) { throw error; } }, examplePayload: { data: { tagged_contacts: 3, }, }, }); /* Action that uses the Cakemail list data source */ const getContactsFromListDropdown = action({ display: { label: "Get Contacts from Selected List", description: "Get contacts from a selected Cakemail list using the dropdown with SQL, JSON, FIQL filtering and sorting options", }, inputs: { cakemailConnection: connectionInput, account_id: input({ label: "Account ID (Optional)", type: "string", required: false, }), selectedList: input({ label: "Select List", type: "string", required: true, }), page: input({ label: "Page", type: "string", required: false, comments: "Page number for pagination", }), per_page: input({ label: "Per Page", type: "string", required: false, comments: "Number of items per page (max 100)", }), with_count: input({ label: "With Count", type: "string", required: false, comments: "Include count in the response (true/false)", }), query: input({ label: "SQL Query", type: "string", required: false, comments: "SQL selection query (only use one of query, fiql and json)", }), json: input({ label: "JSON Filter", type: "code", language: "json", required: false, comments: "JSON selection expression (only use one of query, fiql and json)", example: '{"status": "active", "tags": [1, 2]}', }), fiql: input({ label: "FIQL Filter", type: "string", required: false, comments: "FIQL string (only use one of query, fiql and json)", }), cursor: input({ label: "Cursor", type: "string", required: false, comments: "Cursor pagination", }), sort: input({ label: "Sort", type: "string", required: false, comments: "Sort term and direction using syntax [-|+]term. Valid terms: id, email, status, subscribed_on, last_bounce_type, bounces_count", example: "+email, -subscribed_on", }), filter: input({ label: "Filter", type: "string", required: false, comments: "Filter with valid terms (status, email, tags, interests) and operators (==, !=, >, <, >=, <=). Use ; as separator", example: "status==active;tags==1", }), }, perform: async ( context, { cakemailConnection, account_id, selectedList, page, per_page, with_count, query, json, fiql, cursor, sort, filter, } ) => { const cakemailClient = createCakemailClient(cakemailConnection); // Priority: input account_id, fallback to connection account_id const finalAccountId = account_id || cakemailConnection.fields.account_id; // Build query parameters object, only including defined values const queryParams = buildQueryParams({ account_id: finalAccountId, ...(page ? { page } : {}), ...(per_page ? { per_page } : {}), ...(with_count ? { with_count } : {}), ...(query ? { query } : {}), ...(json ? { json } : {}), ...(fiql ? { fiql } : {}), ...(cursor ? { cursor } : {}), ...(sort ? { sort } : {}), ...(filter ? { filter } : {}), }); try { const response = await cakemailClient.get( `/lists/${selectedList}/contacts${queryParams}` ); return { data: response.data, selectedListId: selectedList, }; } catch (error) { throw error; } }, examplePayload: { data: { data: [ { id: 1, email: "test@test.com", status: "active", subscribed_on: 1746820256, last_bounce_type: "none", bounces_count: 0, custom_attributes: [], tags: [], interests: [], }, ], pagination: { page: 1, per_page: 10, count: 1, }, }, selectedListId: "list_123", }, }); export default { getWebhooks, getContactsOfAList, rawRequest, addContact, deleteContact, updateContact, unsubscribeContact, tagMultipleContacts, importContacts, getTemplates, submitEmail, // Custom Attributes getCustomAttributes, createCustomAttribute, deleteCustomAttribute, // Tags getTags, createTag, deleteTag, // Interests getInterests, createInterest, deleteInterest, // Webhook Management createWebhook, updateWebhook, deleteWebhook, getContactsFromListDropdown, getAccount, getAccountStats, getUser, getCurrentAccount, listCampaigns, // User Management listUsers, //Lists getLists, listLists, createList, // Automations getAutomations, };