Inbound email

Email to webhook, on your own domain

Every email sent to an address on your domain becomes a signed JSON POST to your endpoint: parsed sender, subject, text, HTML, headers and attachments, with no server and no polling.

Facts checked

In short

MarkRelay turns every email sent to an address on your domain into a signed HTTP POST. Cloudflare Email Routing receives the message, MarkRelay parses and stores it in your own Cloudflare account, and an email.received event with the sender, recipients, subject, text, HTML, headers and attachment list goes to your endpoint, signed with HMAC-SHA256. Use it to feed n8n, Zapier, Make, a ticketing system or an AI agent, then reply from the same address over the API.

What the webhook does, line by line

Trigger

MarkRelay
Every new message in the mailboxes the webhook covers

Event

MarkRelay
email.received (plus webhook.test from the dashboard)

Payload

MarkRelay
JSON: id, mailbox, from, to, cc, replyTo, subject, text, html, headers, messageId, inReplyTo, references, threadId, attachments

Attachments

MarkRelay
Name, type and size in the payload; download with GET /attachments/{id}

Signature

MarkRelay
HMAC-SHA256 of timestamp + "." + raw body, in X-MarkRelay-Signature

Delivery

MarkRelay
At least once; 10-second timeout; retried up to 3 times

Failing endpoints

MarkRelay
Paused after 20 failures in a row; re-enable after a passing test

Scope

MarkRelay
Its owner's access, one domain, or selected mailboxes

Team chats

MarkRelay
Or post a short notice to Slack, Discord, Microsoft Teams, Google Chat, Telegram, Feishu/Lark, DingTalk or WeCom

Endpoint

MarkRelay
Public HTTP or HTTPS URL; redirects and private addresses are refused

Reply from the workflow

MarkRelay
POST /emails/{id}/reply with an API key

Price

MarkRelay
No per-event fee; receiving through Email Routing is free

Needs

MarkRelay
A domain on Cloudflare DNS (Free plan works)

Parsed JSON

Sender, recipients, subject, text, HTML, headers and attachments.

Signed

HMAC-SHA256 over the timestamp and raw body.

Retried

At-least-once delivery, with a log in the dashboard.

Scoped

Each webhook covers only the mailboxes you choose.

How a message becomes a webhook

Cloudflare Email Routing hands each incoming message to the MarkRelay Worker in your account. MarkRelay parses it, stores the message in D1 and attachments in R2, and queues one delivery for every webhook that covers that mailbox.

The payload is built when it is delivered, signed, and POSTed to your endpoint. Return any 2xx status to acknowledge it; anything else is retried.

How mail flows through your Cloudflare account

Cloudflare receives and sends. MarkRelay is the mailbox in between, running as your own Worker.

Receive

  1. Senderwrites to support@yourdomain.com
  2. Email RoutingCloudflare, free on any plan
  3. MarkRelay Workerfiles it in the mailbox
  4. Your D1 + R2messages and attachments

Reply

  1. Webmail or APIa teammate, an app or an agent
  2. MarkRelay Workerchecks mailbox access
  3. Email SendingCloudflare, in Workers Paid
  4. Recipientsees support@yourdomain.com

Verify every request

Each request carries X-MarkRelay-Event, X-MarkRelay-Webhook-Id, X-MarkRelay-Timestamp and X-MarkRelay-Signature. The signature is sha256= followed by the hex HMAC-SHA256 of the timestamp, a dot, and the exact raw body, keyed with the webhook's whsec_ secret.

Check it before parsing, reject old timestamps, and use the email id to ignore a delivery you have already processed.

  • Signed with a per-webhook secret
  • Replay window under your control
  • Dedupe on the email id
  • Send test from the dashboard

Plug it into the tools you use

Any tool that accepts an incoming webhook can take MarkRelay's events: n8n's Webhook node, Webhooks by Zapier, Make's custom webhook, or your own Worker. Each step-by-step guide below covers receiving the event, checking the signature and replying.

  • n8n: Webhook node + Crypto node
  • Zapier: Catch Raw Hook + Code step
  • Make: custom webhook + sha256()
  • Your code: a few lines of Web Crypto

Scope each webhook to the mail it should see

A webhook can follow its owner's access, cover one domain, or include only selected mailboxes, so the workflow for orders@ never sees mail for hr@. If the owner loses access to a mailbox, the webhook stops receiving its mail at once.

mail.yourdomain.com
MarkRelay webhooks page with two signed endpoints, each scoped to specific mailboxes
Signed webhooks, each limited to the mailboxes it should hear about.

What your endpoint receives

email.received.json
{
  "event": "email.received",
  "webhookId": "wh_...",
  "timestamp": "2026-10-17T09:30:00.000Z",
  "data": {
    "id": "em_...",
    "mailbox": "orders@acme.com",
    "folder": "inbox",
    "direction": "incoming",
    "from": { "name": "Dana Lee", "address": "dana@example.com" },
    "to": [{ "name": null, "address": "orders@acme.com" }],
    "cc": [], "replyTo": [],
    "subject": "PO 4471 attached",
    "snippet": "Hi, please find the purchase order…",
    "text": "Hi, please find the purchase order attached.",
    "html": "<p>Hi, please find the purchase order attached.</p>",
    "headers": { "message-id": "<…@example.com>", "...": "..." },
    "messageId": "<…@example.com>",
    "inReplyTo": null,
    "references": [],
    "threadId": "th_...",
    "receivedAt": "2026-10-17T09:29:58.000Z",
    "attachments": [
      { "id": "att_...", "fileName": "PO-4471.pdf", "contentType": "application/pdf", "size": 48213, "disposition": "attachment" }
    ]
  }
}

Verify the signature

worker.ts
// A Cloudflare Worker (or any fetch handler) that receives MarkRelay's
// email.received event and checks its signature before trusting it.
export default {
  async fetch(request: Request, env: { MARKRELAY_WEBHOOK_SECRET: string }) {
    const body = await request.text(); // the exact raw body
    const timestamp = request.headers.get("X-MarkRelay-Timestamp") ?? "";
    const signature = (request.headers.get("X-MarkRelay-Signature") ?? "").replace(/^sha256=/, "");

    const key = await crypto.subtle.importKey(
      "raw",
      new TextEncoder().encode(env.MARKRELAY_WEBHOOK_SECRET), // whsec_...
      { name: "HMAC", hash: "SHA-256" },
      false,
      ["verify"],
    );
    const bytes = new Uint8Array((signature.match(/../g) ?? []).map((hex) => parseInt(hex, 16)));
    const valid = await crypto.subtle.verify("HMAC", key, bytes, new TextEncoder().encode(`${timestamp}.${body}`));
    if (!valid) return new Response("invalid signature", { status: 401 });

    const { event, data } = JSON.parse(body);
    // data: { id, mailbox, from, to, cc, subject, text, html, headers, attachments, ... }
    console.log(event, data.mailbox, data.from.address, data.subject);
    return new Response("ok"); // any 2xx counts as delivered
  },
};

Common questions

How do I forward email to a webhook?

Point the address's Cloudflare Email Routing rule at the MarkRelay Worker, then add a webhook in Developer → Webhooks, choose Custom endpoint and paste your endpoint URL. Every new message then arrives at your endpoint as a signed email.received POST with the parsed message.

Does the payload include the email body and attachments?

It includes the text and HTML bodies, all headers, and each attachment's id, name, type and size. Download attachment files with GET /attachments/{id} using an API key.

What happens if my endpoint is down?

Delivery is retried up to 3 times. After 20 failures in a row the webhook is paused; fix the endpoint, send a test from the dashboard and turn it back on. Recent results are listed in the dashboard.

Can new email go straight to Slack or another team chat?

Yes. Choose Slack, Discord, Microsoft Teams, Google Chat, Telegram, Feishu/Lark, DingTalk or WeCom as the destination and paste the platform's incoming-webhook URL. Each new email is posted with its sender, mailbox, subject and a short preview, and Feishu/Lark and DingTalk bots with signature verification are signed the platform's way.

Can I use it with n8n, Zapier or Make?

Yes. Each accepts incoming webhooks: n8n's Webhook node, Webhooks by Zapier's Catch Raw Hook, and Make's custom webhook. The guides show how to check the signature and reply from the workflow.

How is this different from an email parser service?

The mailbox is yours: it runs on your own domain in your own Cloudflare account, keeps every message so people can read it in webmail, and lets the workflow reply from the same address. There is no per-email fee from MarkRelay.

Go deeper

Explore more of MarkRelay

Use cases

Own your email stack today

Buy MarkRelay once and run it in your Cloudflare account. MarkRelay adds no per-message or per-seat charge; Cloudflare usage and service limits remain separate.