Skip to content

Deliver mail to a webhook

POST each arriving message to Discord, Slack, or a signed endpoint you control — push delivery instead of polling.

A webhook turns an inbox from pull to push: every message that lands on a handle is delivered to an HTTPS endpoint you choose. Webhooks are a paid feature (pricing) and are managed by the org owner, one endpoint per handle.

When you add a webhook you pick a format:

Format Where it POSTs Signed? Use when
discord A Discord channel webhook URL No You just want to see mail land — paste the URL, done
slack A Slack incoming-webhook URL No Same, for Slack
raw Any HTTPS endpoint you control Yes You’re building an integration and want to verify authenticity

Discord and Slack need zero code: create the webhook in the chat app, paste its URL, and MailMatter reshapes each message into the native payload. No secret is involved because those platforms can’t verify signatures.

1. Create the endpoint

Open Settings → Webhooks as the org owner, pick the handle, press New webhook, and paste the destination URL. Webhooks are configured in the dashboard rather than over the API — see Managed in the dashboard.

Settings → Webhooks: the handle picker, the endpoint with its format and delivery status, and the send-test action

For the raw format the screen shows a signing secret (whsec_…) exactly once. Store it now — it’s the only way to verify deliveries later, and it can’t be shown again.

2. Send a test delivery

Press Send test. MailMatter queues a synthetic delivery to the endpoint in the same shape as a real one, and the row’s delivery status updates with the response code your receiver returned.

3. Handle the raw payload

Each delivery POSTs the canonical JSON:

{
  "event": "message.received",
  "event_id": "evt_…",
  "occurred_at": 1721400000000,
  "handle": "bluepine",
  "scenario": "signup",
  "message": {
    "id": "msg_…",
    "from": "no-reply@app.example",
    "subject": "Your code is 481920",
    "received_at": 1721399999000,
    "text": "…",
    "text_truncated": false,
    "links": ["https://app.example/verify?t=abc"],
    "has_html": true,
    "attachment_count": 0
  },
  "urls": {
    "message": "https://mailmatter.dev/api/message/msg_…",
    "raw": "https://mailmatter.dev/api/message/msg_…/raw"
  }
}

event_id is stable per (endpoint, message) — use it to dedupe retries. Any 2xx acknowledges the delivery; anything else is retried, and an endpoint that keeps failing is automatically disabled (re-enable it from the dashboard once your receiver is fixed).

4. Verify the signature

Every raw delivery carries a Mailmatter-Signature header:

Mailmatter-Signature: t=<unix-seconds>,v1=<hmac>

where v1 is HMAC-SHA256(signing_secret, "<t>.<rawBody>") as hex. Verification is optional to get started — you can just read the JSON — but recommended for production, since your URL is effectively public:

verify.jsjs
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // reject stale (replay)
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

// Express: pass the RAW body, not the parsed object.
app.post("/hook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body.toString(), req.get("Mailmatter-Signature"), process.env.MM_SECRET))
    return res.sendStatus(401);
  const event = JSON.parse(req.body.toString());
  // …handle event.message…
  res.sendStatus(200);
});

Verify

Send an email to <handle>.<any-scenario>@mailmatter.dev. Your endpoint (or chat channel) should receive the delivery within seconds, and the endpoint row in Settings → Webhooks shows the status code of the most recent attempt. If the New webhook action is unavailable, the plan doesn’t include webhooks or you aren’t the org owner.

Next steps

  • Testing with MailMatter — polling done right, when a webhook receiver is more machinery than the test deserves.
  • REST API — reading the delivered message by id.