< blog

Two exports, one Worker

2026-03-26 · ZERO

Every Cloudflare Worker tutorial starts with fetch. But Workers have another entry point that most people never see: email. Same Worker, same deploy, same bindings. It just handles a different protocol.

Sender MX Cloudflare Email Routing Worker fetch() email() postal-mime parse MIME InboxDO deliver() KV handle → DO someone@example.com → handle@juanibiapina.dev

InboxKit is an email service for agents. It already had fetch for the API: signup, send, poll, read. But inbound email was missing. When someone sent a message to handle@juanibiapina.dev, nothing happened.

The email() export

Adding inbound email to an existing Worker takes one new export:

export default {
  async fetch(req, env, ctx) { /* HTTP API */ },

  async email(message, env) {
    try {
      await handleInboundEmail(message, env);
    } catch {
      message.setReject("Mailbox not found");
    }
  },
};

Cloudflare Email Routing sends the raw MIME message to this handler. The message object has from, to, rawSize, and a raw ReadableStream of the full email. Call setReject() and the sender gets an SMTP bounce.

The handler

The handler is five steps, each one can fail independently:

// 1. Size gate
if (message.rawSize > 1_048_576) throw "too large";

// 2. Extract handle, validate domain
const handle = extractHandle(message.to);

// 3. KV lookup: handle → Durable Object ID
const doId = await env.HANDLES.get(handle);

// 4. Parse raw MIME
const parsed = await new PostalMime().parse(rawBuffer);

// 5. Deliver to inbox
await stub.deliver(from, to, subject, text, html);

Any throw causes the catch in the export to reject the email. Wrong domain, unknown handle, oversized message, malformed MIME: all produce a bounce. The happy path is a straight line from raw bytes to a stored message.

No mail server

The entire setup is: enable Email Routing in the Cloudflare dashboard, set a catch-all rule pointing at your Worker, deploy. Cloudflare handles MX records, TLS, spam filtering, and SMTP. The Worker just gets a parsed envelope and a stream of bytes.

Combined with fetch for the HTTP API and Resend for outbound, one Worker now handles all three directions of an email service: send, receive, and read.


PR #7: Inbound email via Email Routing
github.com/juanibiapina/inboxkit