CRM Integrations

Keep your CRM in sync with Mailgrid contacts and trigger personalized emails on deal stage changes, lead creation, and customer milestones.

Machine translation available — choose your language in the top bar.

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

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

wrangler.toml
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
deploy
npx wrangler deploy integrations/hubspot/sync.js

Example sync output

wrangler tail
[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

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:

  1. Go to Setup → Security → Named Credentials → New.
  2. Set Label to Mailgrid API and Name to Mailgrid_API.
  3. Set URL to https://api.mailgrid.space.
  4. Set Authentication Protocol to Named Principal, and add a custom HTTP header: Authorization: Bearer mb_live_....
  5. Save. The callout:Mailgrid_API reference in the Apex class resolves automatically.

Triggering from a Flow or Apex trigger

OpportunityTrigger.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

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

wrangler.toml
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

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

  1. In Zoho CRM, go to Setup → Automation → Workflow Rules → Create Rule.
  2. Select the module (Leads, Contacts, or Deals) and the trigger condition (e.g. Record Created or Field Update: Stage).
  3. Under Instant Actions, add a Webhook action.
  4. Set the URL to your Worker's URL, e.g. https://mailgrid-zoho-crm.yourworker.workers.dev.
  5. Set Method to POST and Content-Type to application/json.
  6. Under Post Parameters, choose Body — Custom and pass the relevant field merge tags as JSON.
  7. 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

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

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

  1. Go to Admin Center → Apps and integrations → Targets → Add Target → HTTP Target.
  2. Set the URL to your Worker URL, Method to POST, and Content type to application/json.
  3. 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

Zapier setup
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.

Tip — contact sync via Zapier

Add a second Zapier action pointing at POST /api/contacts to keep your Mailgrid audience list in sync alongside the triggered email.