CRM Integrations
Keep your CRM in sync with Mailgrid contacts and trigger personalized emails on deal stage changes, lead creation, and customer milestones.
HubSpot
The HubSpot integration is a Cloudflare Worker that runs on a cron schedule, polls the HubSpot contacts/search API for records modified since the last run, and upserts them into InboxOS via POST /api/contacts.
Worker: integrations/hubspot/sync.js
// Cloudflare Worker — scheduled HubSpot → Mailgrid contact sync export default { async scheduled(event, env, ctx) { const lastRun = (await env.STATE.get("hubspot:lastRun")) ?? "0"; // 1. Fetch contacts modified since last run const hsRes = await fetch( "https://api.hubapi.com/crm/v3/objects/contacts/search", { method: "POST", headers: { "Authorization": `Bearer ${env.HUBSPOT_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ filterGroups: [{ filters: [{ propertyName: "lastmodifieddate", operator: "GTE", value: lastRun, }], }], properties: ["email", "firstname", "lastname", "company"], limit: 100, }), } ); const { results } = await hsRes.json(); // 2. Sync each contact to Mailgrid InboxOS for (const contact of results) { const { email, firstname, lastname, company } = contact.properties; await fetch("https://api.mailgrid.space/api/contacts", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ email, name: `${firstname} ${lastname}`.trim(), company, source: "hubspot", }), }); } // 3. Persist the new high-water mark await env.STATE.put("hubspot:lastRun", String(Date.now())); console.log(`Synced ${results.length} HubSpot contacts`); }, };
Environment variables
name = "mailgrid-hubspot-sync" main = "integrations/hubspot/sync.js" compatibility_date = "2024-09-23" [vars] MAILGRID_API_KEY = "mb_live_..." # or use wrangler secret put HUBSPOT_TOKEN = "pat-na1-..." [[kv_namespaces]] binding = "STATE" id = "<your-kv-namespace-id>" [triggers] crons = ["*/15 * * * *"] # every 15 minutes
npx wrangler deploy integrations/hubspot/sync.js
Example sync output
[2026-06-05T09:00:00Z] Synced 12 HubSpot contacts
[2026-06-05T09:15:00Z] Synced 3 HubSpot contacts
[2026-06-05T09:30:00Z] Synced 0 HubSpot contacts
Salesforce
The Salesforce integration uses an Apex class with a static send method that calls out to the Mailgrid API. Invoke it from a Process Builder, Flow, or any Apex trigger.
Apex class: integrations/salesforce/apex/MailgridService.cls
public class MailgridService { /** * Send a transactional email via Mailgrid. * @param email Serialisable payload — see MailgridEmail inner class. */ public static void send(MailgridEmail email) { HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Mailgrid_API/api/emails'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(JSON.serialize(email)); Http http = new Http(); HttpResponse res = http.send(req); if (res.getStatusCode() != 200) { throw new CalloutException( 'Mailgrid error ' + res.getStatusCode() + ': ' + res.getBody() ); } } public class MailgridEmail { public String fromAddress; public String toAddress; public String subject; public String text; public String html; public String templateId; public Map<String, Object> variables; } }
Setting up Named Credentials
Salesforce uses Named Credentials to store the API key so it never appears in Apex code:
- Go to Setup → Security → Named Credentials → New.
- Set Label to
Mailgrid APIand Name toMailgrid_API. - Set URL to
https://api.mailgrid.space. - Set Authentication Protocol to Named Principal, and add a custom HTTP header:
Authorization: Bearer mb_live_.... - Save. The
callout:Mailgrid_APIreference in the Apex class resolves automatically.
Triggering from a Flow or Apex trigger
trigger OpportunityTrigger on Opportunity (after update) { for (Opportunity opp : Trigger.new) { Opportunity old = Trigger.oldMap.get(opp.Id); if (opp.StageName == 'Closed Won' && old.StageName != 'Closed Won') { MailgridService.MailgridEmail email = new MailgridService.MailgridEmail(); email.fromAddress = 'success@yourco.com'; email.toAddress = opp.Contact__c; // lookup field email.subject = 'Welcome aboard, ' + opp.Account.Name + '!'; email.templateId = 'tpl_welcome_closeWon'; email.variables = new Map<String, Object>{ 'deal_name' => opp.Name, 'amount' => opp.Amount }; MailgridService.send(email); } } }
Pipedrive
The Pipedrive integration is a Cloudflare Worker that runs on a schedule. It polls /v1/persons for new or updated contacts since the last run, syncs them to Mailgrid, and also watches for deal stage changes to fire notification emails.
Worker: integrations/pipedrive/sync.js
export default { async scheduled(event, env, ctx) { const since = (await env.STATE.get("pipedrive:since")) ?? "2020-01-01 00:00:00"; // 1. Sync persons (contacts) const personsRes = await fetch( `https://api.pipedrive.com/v1/persons?since_timestamp=${encodeURIComponent(since)}&api_token=${env.PIPEDRIVE_API_TOKEN}` ); const { data: persons = [] } = await personsRes.json(); for (const person of persons) { const email = person.email?.[0]?.value; if (!email) continue; await fetch("https://api.mailgrid.space/api/contacts", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ email, name: person.name, source: "pipedrive" }), }); } // 2. Check for deal stage changes const dealsRes = await fetch( `https://api.pipedrive.com/v1/deals?since_timestamp=${encodeURIComponent(since)}&api_token=${env.PIPEDRIVE_API_TOKEN}` ); const { data: deals = [] } = await dealsRes.json(); for (const deal of deals) { const prevStage = await env.STATE.get(`deal:${deal.id}:stage`); if (prevStage && prevStage !== deal.stage_id) { // Stage changed — send notification email await fetch("https://api.mailgrid.space/api/emails", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "deals@yourco.com", to: deal.person_id?.email?.[0]?.value, subject: `Your deal has moved: ${deal.title}`, templateId: "tpl_deal_stage_change", variables: { deal_title: deal.title, new_stage: deal.stage_id }, }), }); } await env.STATE.put(`deal:${deal.id}:stage`, deal.stage_id); } const now = new Date().toISOString().replace("T", " ").slice(0, 19); await env.STATE.put("pipedrive:since", now); console.log(`Synced ${persons.length} persons, checked ${deals.length} deals`); }, };
Environment variables
name = "mailgrid-pipedrive-sync" main = "integrations/pipedrive/sync.js" compatibility_date = "2024-09-23" [vars] PIPEDRIVE_API_TOKEN = "..." MAILGRID_API_KEY = "mb_live_..." [[kv_namespaces]] binding = "STATE" id = "<your-kv-namespace-id>" [triggers] crons = ["*/10 * * * *"]
Zoho CRM
The Zoho CRM integration is a Cloudflare Worker that receives real-time webhook events from Zoho CRM workflow rules. It handles the Leads, Contacts, and Deals modules.
Worker: integrations/zoho-crm/webhook.js
export default { async fetch(req, env) { if (req.method !== "POST") return new Response("ok"); const payload = await req.json(); // Zoho sends an array of records under the module name const module = payload.module; // "Leads" | "Contacts" | "Deals" const records = payload.data ?? []; for (const record of records) { const email = record.Email; if (!email) continue; if (module === "Leads" || module === "Contacts") { // Upsert contact in Mailgrid await fetch("https://api.mailgrid.space/api/contacts", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ email, name: `${record.First_Name} ${record.Last_Name}`.trim(), company: record.Company, source: "zoho-crm", tags: [module.toLowerCase()], }), }); } else if (module === "Deals") { // Trigger a deal-stage email await fetch("https://api.mailgrid.space/api/emails", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "deals@yourco.com", to: email, templateId: "tpl_deal_update", variables: { deal_name: record.Deal_Name, stage: record.Stage }, }), }); } } return new Response("ok"); }, };
Setting up a Zoho CRM workflow rule
- In Zoho CRM, go to Setup → Automation → Workflow Rules → Create Rule.
- Select the module (Leads, Contacts, or Deals) and the trigger condition (e.g. Record Created or Field Update: Stage).
- Under Instant Actions, add a Webhook action.
- Set the URL to your Worker's URL, e.g.
https://mailgrid-zoho-crm.yourworker.workers.dev. - Set Method to
POSTand Content-Type toapplication/json. - Under Post Parameters, choose Body — Custom and pass the relevant field merge tags as JSON.
- Save and activate the rule. Zoho will POST the payload to your Worker on every trigger.
Intercom
The Intercom integration is a Cloudflare Worker that receives Intercom webhook events and triggers targeted Mailgrid email campaigns based on user actions and tags.
Worker: integrations/intercom/webhook.js
// Supported Intercom events: // conversation.user.replied → follow-up drip // user.created → welcome email // user.tag.created → tag-based campaign const CAMPAIGN_MAP = { "trial_started": "tpl_trial_welcome", "churned": "tpl_win_back", "power_user": "tpl_upsell_pro", }; export default { async fetch(req, env) { const { topic, data } = await req.json(); switch (topic) { case "user.created": { const { email, name } = data.item; await sendEmail(env, email, name, "tpl_welcome", {}); await upsertContact(env, email, name, ["intercom"]); break; } case "user.tag.created": { const { user, tag } = data.item; const tpl = CAMPAIGN_MAP[tag.name]; if (tpl) await sendEmail(env, user.email, user.name, tpl, { tag: tag.name }); break; } case "conversation.user.replied": { const contact = data.item.user; await sendEmail(env, contact.email, contact.name, "tpl_reply_followup", {}); break; } } return new Response("ok"); }, }; async function sendEmail(env, to, name, templateId, variables) { return fetch("https://api.mailgrid.space/api/emails", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "hello@yourco.com", to, templateId, variables }), }); } async function upsertContact(env, email, name, tags) { return fetch("https://api.mailgrid.space/api/contacts", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ email, name, tags, source: "intercom" }), }); }
Register your Worker URL as an Intercom webhook in Settings → Integrations → Webhooks. Subscribe to user.created, user.tag.created, and conversation.user.replied topics.
Zendesk
The Zendesk integration uses a Cloudflare Worker as an HTTP Target. Zendesk triggers call the Worker on ticket events; the Worker dispatches the appropriate Mailgrid email — an acknowledgement when a ticket is created, and a satisfaction survey when it is solved.
Worker: integrations/zendesk/webhook.js
export default { async fetch(req, env) { const { event, ticket } = await req.json(); const requesterEmail = ticket.requester?.email; if (!requesterEmail) return new Response("no email"); if (event === "ticket.created") { await fetch("https://api.mailgrid.space/api/emails", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "support@yourco.com", to: requesterEmail, subject: `[Ticket #${ticket.id}] We received your request`, templateId: "tpl_ticket_ack", variables: { ticket_id: ticket.id, ticket_subject: ticket.subject, requester_name: ticket.requester?.name, }, }), }); } if (event === "ticket.solved") { await fetch("https://api.mailgrid.space/api/emails", { method: "POST", headers: { "Authorization": `Bearer ${env.MAILGRID_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "support@yourco.com", to: requesterEmail, subject: `How did we do? [Ticket #${ticket.id}]`, templateId: "tpl_csat_survey", variables: { ticket_id: ticket.id, ticket_subject: ticket.subject, survey_link: `https://yourco.com/csat/${ticket.id}`, }, }), }); } return new Response("ok"); }, };
Registering the HTTP Target in Zendesk
- Go to Admin Center → Apps and integrations → Targets → Add Target → HTTP Target.
- Set the URL to your Worker URL, Method to
POST, and Content type toapplication/json. - Create a Trigger for each event (Ticket created, Ticket solved) and point it at the HTTP Target with a JSON body including
{"event": "ticket.created", "ticket": {"id": "{{ticket.id}}", ...}}.
No-code CRM (via Zapier / Make)
If your CRM is not listed above — or you prefer a no-code setup — Mailgrid works out of the box through Zapier. This covers Notion, Monday.com, Freshsales, Airtable, and hundreds of other tools that have Zapier integrations.
Zapier two-step example: Airtable → Mailgrid
Step 1 — Trigger: Airtable App: Airtable Event: New Record in View Table: Leads View: "New this week" Step 2 — Action: Webhooks by Zapier App: Webhooks by Zapier Event: POST URL: https://api.mailgrid.space/api/emails Headers: Authorization: Bearer mb_live_... Content-Type: application/json Data: { "from": "hello@yourco.com", "to": "{{1.Email}}", "subject": "Welcome, {{1.Name}}!", "templateId": "tpl_lead_welcome", "variables": { "name": "{{1.Name}}", "company": "{{1.Company}}" } }
The same pattern applies to Make (formerly Integromat) — use the HTTP → Make a request module with the same URL and headers.
Add a second Zapier action pointing at POST /api/contacts to keep your Mailgrid audience list in sync alongside the triggered email.