API keys and webhooks
Mail Merge has a REST API and outgoing webhooks for developers and no-code builders. With an API key your scripts can do what the web app does: read your account, list templates and contacts, send campaigns and follow their recipients. With a webhook Mail Merge calls your endpoint the moment a campaign finishes or a recipient opens, clicks, replies, bounces or unsubscribes, so a CRM, a spreadsheet or a Slack channel stays in step without polling.
Both live under Settings → Developers in the web app and are part of the Professional plan (see Pricing). If you would rather talk to Mail Merge from an AI assistant than write code, use the MCP server instead: it exposes the same features as tools, on every plan.
API keys
Section titled “API keys”Create a key
Section titled “Create a key”- Open Settings → Developers → API keys and click Create key.
- Name it after where it will be used (
Zapier,CRM sync), so you can revoke the right one later. - Copy the key. It is shown once: Mail Merge keeps only a hash of it. If you lose it, revoke it and create a new one.
You can have up to 10 keys per account. Revoking a key fails every request made with it immediately; the row disappears from the list.
Authenticate
Section titled “Authenticate”Send the key as a Bearer token on every request, against the base URL shown on the card:
curl https://merge.email/api/v1/me \ -H "Authorization: Bearer mm_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"A key acts as the account that created it, exactly like a signed-in session: the same Google grant, the same daily quota, the same plan limits. There is no separate “API user”.
Endpoints
Section titled “Endpoints”The API is the same /v1 surface the web app uses, so anything you can do on a screen has an endpoint behind it. The ones integrations reach for most:
| Call | What it does |
|---|---|
GET /v1/me | The account: plan, daily quota (max, used, remaining), timezone, template metadata. |
GET /v1/templates · GET /v1/templates/{templateId} | Your saved templates; the body loads per template. |
GET /v1/contacts · POST /v1/contacts | The contact book (paginated with limit / cursor → nextCursor). |
POST /v1/campaigns | Send, schedule or autopilot a campaign from a Google Sheet. The body is the same one the campaign builder posts: the sheet, the recipient column, the subject and body with {{merge tags}}, and the sending options. |
GET /v1/campaigns · GET /v1/campaigns/{campaignId}/recipients | Your campaigns and each recipient’s status (sent, read, clicked, replied, bounced, unsubscribed). |
POST /v1/campaigns/{campaignId}/status | Pause, resume or stop a running campaign. |
POST /v1/recipients/verify | The email verifier. |
Request and response bodies are JSON. The full typed surface is generated from the server as an OpenAPI document; contact us if you need the schema for a client generator.
Errors and rate limit
Section titled “Errors and rate limit”Failures are real HTTP statuses with one envelope:
{ "error": { "code": "professional_plan_required", "message": "API access is a Professional plan feature" } }| Status | code | Meaning |
|---|---|---|
| 401 | Unknown or revoked key, or the account’s Google access needs reconnecting. | |
| 403 | professional_plan_required | The key’s account is no longer on the Professional plan. Keys are not deleted on a downgrade; they work again when the plan does. |
| 429 | rate_limited | More than 600 requests per minute on one key. Wait a minute and retry. |
| 400 / 404 / 409 | bad_request, not_found, conflict, … | The request itself, with a message saying what to fix. |
The rate limit is counted per key on each API server instance, so the effective ceiling is “about 600 per minute”: do not design around hitting it.
Webhooks
Section titled “Webhooks”Add an endpoint
Section titled “Add an endpoint”- Open Settings → Developers → Webhooks and click Add webhook.
- Enter an HTTPS URL (plain
http://is refused) and tick the events it should receive. - Click Send test on the new row: Mail Merge posts a
pingand the toast tells you how your endpoint answered.
Each endpoint gets its own signing secret (whsec_…), shown on the row with a copy button. You can have up to 5 endpoints per account.
Events
Section titled “Events”| Event | When | data |
|---|---|---|
campaign.completed | The last email of a campaign was processed. | campaignId, name, status: "completed", sentCount, campaignSize |
campaign.paused · campaign.resumed · campaign.stopped | You paused, resumed or stopped a campaign. | campaignId, name, status (paused / active / stopped), sentCount |
email.opened | A recipient opened an email, first time only per campaign. | campaignId, recipient, at |
email.clicked | A recipient clicked a tracked link, every click. | campaignId, recipient, at, url |
email.replied | A reply was detected on a sent thread. | campaignId, recipient, at |
email.bounced | A bounce was detected. | campaignId, recipient, at |
recipient.unsubscribed | A recipient used the unsubscribe link, first time only per campaign. | campaignId, recipient, at, method |
Opens and clicks need tracking on for the campaign; replies and bounces come from the reply scan that follows every sent campaign.
Delivery
Section titled “Delivery”Mail Merge sends one POST per event per subscribed endpoint, with a JSON body:
{ "id": "6f1c2d3e-…", "event": "email.opened", "createdAt": "2026-10-15T14:03:21.512Z", "data": { "campaignId": "…", "recipient": "ada@example.com", "at": "2026-10-15T14:03:21.498Z" }}and three headers:
| Header | Value |
|---|---|
X-MailMerge-Event | The event name, same as event in the body. |
X-MailMerge-Delivery | The delivery id, same as id. Use it to ignore a duplicate. |
X-MailMerge-Signature | sha256= followed by the hex HMAC-SHA256 of the raw body, keyed with the endpoint’s secret. |
Answer with any 2xx within 5 seconds. On a network error or a 5xx Mail Merge retries once, after 2 seconds; a 4xx is taken as your verdict and not retried. The same delivery id is sent on the retry. After 20 failed deliveries in a row the endpoint is disabled and the row says so: fix the endpoint, then delete and add it again. Any successful delivery resets the counter.
Events are delivered at least once and in no guaranteed order. Opens in particular arrive in bursts (every mail client that renders the tracking pixel), so make your handler idempotent on X-MailMerge-Delivery or on (event, campaignId, recipient).
Verify the signature
Section titled “Verify the signature”Always verify before trusting a payload. Compute the HMAC over the raw request body (the exact bytes, before any JSON parsing) and compare it in constant time:
// Node.js (Express with `express.raw({ type: "application/json" })`)import { createHmac, timingSafeEqual } from "node:crypto"
export function verifyMailMergeSignature(rawBody, signatureHeader, secret) { const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex") const a = Buffer.from(expected) const b = Buffer.from(signatureHeader ?? "") return a.length === b.length && timingSafeEqual(a, b)}
app.post("/mailmerge", (req, res) => { if (!verifyMailMergeSignature(req.body, req.get("X-MailMerge-Signature"), process.env.MAILMERGE_WEBHOOK_SECRET)) { return res.status(401).end() } const payload = JSON.parse(req.body) // … handle payload.event / payload.data res.status(204).end()})# Pythonimport hashlib, hmac
def verify_mailmerge_signature(raw_body: bytes, signature_header: str, secret: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature_header or "")Zapier, Make and n8n
Section titled “Zapier, Make and n8n”No code needed: create a Catch Hook (Zapier Webhooks by Zapier), a Custom webhook (Make) or a Webhook trigger node (n8n), paste the URL it gives you as the endpoint, tick the events, and click Send test so the tool can sample the payload. The data fields become the variables you map into the next step (add a row to a sheet, update a CRM contact, post to Slack). Those tools do not verify signatures out of the box; the URL they give you is unguessable, which is the protection they rely on.
Where to go next
Section titled “Where to go next”- AI agents (MCP), if an assistant should drive Mail Merge for you instead of a script.
- Campaigns, for what the statuses in the recipients endpoint mean.
- Pricing, for what the Professional plan includes.