Blog

/

Custom Email Routing on Cloudflare: Build Your Own Address Book

Catch-all email routing is great for “any address at this domain lands in my inbox.” It’s less great when you want to declare “only these addresses land, and the rest bounce.” This post is the long-form answer to the question we get most often from teams onboarding Flowmails — what a route table is, where the rules actually live, and how to switch between catch-all and explicit routing without losing mail mid-flight.

Route tables
Cloudflare Email Routing
Allow-lists

Why catch-all stops being enough

Cloudflare Email Routing has two operating modes. The catch-all forwards every *@yourdomain to a single action — usually a Worker that lands the mail in your D1. It’s the default at onboarding, and for the first few weeks of a domain’s life it’s exactly what you want: you don’t know which addresses matter yet, so you let everything through.

The trouble starts when which addresses matter becomes a question. Maybe you want support@ and press@ to land in the inbox but careers@ and webmaster@ to bounce so the long tail of spam doesn’t fill your storage. Or you want a different group of addresses per domain in the same Cloudflare account. A single catch-all rule on a zone can’t express that — you need an allow-list, and something has to enforce it.

The route table, and why we call it a table

A route table is a named, ordered list of local parts — the bit before the @ in an email address. The name is deliberate: each row pairs a local part with the full local@domain address it will produce, exactly like a column in a SQL table. The list is deduped and sorted, so two rows naming the same address collapse to one entry.

Bind a table to a domain and Flowmails writes those local parts into the shared mail worker, in its key-value store, keyed by that domain’s id. The zone keeps exactly one Cloudflare rule — the catch-all pointing at your routing address — and the worker checks the recipient against the list as each message arrives, rejecting anything not on it with a standard 550 5.1.1 so the sender learns the address doesn’t exist. Nothing in your mail path has to change, and nothing is written to Cloudflare when you edit the list. Unbind and the list is cleared; the domain stops accepting mail at those addresses until you explicitly restore (we don’t auto-revert, because the intent at unbind time is usually “I want a fresh start,” not “please forget that I unbound”).

rule-table.txttext
# A route table is a JSON array of local parts.
# Bind it to a domain to store the allow-list in the shared mail worker.
[
  "support",
  "sales",
  "press"
]

Data flow: from support@ to your inbox

  1. Someone sends mail to support@yourdomain. SMTP resolves to Cloudflare’s MX; the message lands at the Email Routing layer.
  2. The catch-all rule forwards it to your routing address, which lands in the Flowmails mail worker. The worker looks the recipient up in the route table stored for that domain before it parses anything — a miss is rejected with 550 5.1.1 right there.
  3. The worker writes a row to email in D1 with the parsed envelope, then a row to email_address for the recipient, then a row to email_content for the body. Attachments go to Flowmails’ own storage.
  4. Your dashboard’s Emailbox page reads from those tables via the mail worker’s REST API. The top-of-page banner shows the route table that’s bound to the active domain, so a user opening the inbox sees exactly which addresses receive mail.

System presets, hardcoded on purpose

Most teams want one of four shapes. To save everyone the trouble of re-typing the same three local parts, the Flowmails shared package ships four hardcoded presets — no database row, no migration, no seed:

Standard

3

Common support aliases for small businesses.

support, sales, contact

Personal Blog

2

Author-facing aliases for content creators.

author, hello

Startup

3

Lean trio for early-stage products.

support, info, noreply

Press & Media

3

Public relations and partnership aliases.

press, media, partnerships

Picking a preset is a form prefill, not a persistent reference — Flowmails stores only the local parts on the domain’s binding row, never the template id. This means a future renaming of Standard to Standard v2 won’t retroactively change any user’s domain; they always see exactly what they wrote.

Building a custom template

The Route Templates page is a library of your own tables. Creating one starts with a name and a list of local parts; the “Copy from” dropdown lets you seed the editor with a system preset (e.g. Personal Blog) or another one of your templates, then add or remove parts to taste. The result is deduped, sorted, and saved as a row in email_routes_template with a unique (user_id, name) constraint.

Templates never own a domain — they’re just a convenient starting point. Deleting a template doesn’t touch any binding row that was filled from it, because the binding only stored the local-part array.

Bind, unbind, restore — and the warning you’ll see if you skip a step

The three operations sit on a tiny state machine:

  • Bind a table to a domain: Flowmails overwrites the allow-list in the shared worker, then records the binding. One write, no Cloudflare API call — editing your route table doesn’t touch your Cloudflare account at all.
  • Unbind: clears the worker’s list and removes the binding row. The domain is notauto-restored — nothing is accepted at those addresses any more, so mail will bounce until you rebind or click restore.
  • Accept mail at every address (the old “restore catch-all”): flips the worker back to accepting everything at that domain and the routing_mode column back to catchall. The saved table stays in the dashboard so you can re-apply it any time.

A domain in rules mode with zero addresses shows an amber warning telling you mail will bounce until you rebind or restore. The dashboard’s domain detail page adds explicit Unbind and Accept mail at every address buttons so the recovery is one click, not a guessing game, and the domain health check verifies that the worker’s copy of the table still matches what the dashboard is showing you.

Try it

Route tables ship as a first-class part of every Flowmails account. The fastest path is during onboarding — after your domain is connected, the receiving step offers a picker that previews the system presets. From the dashboard, open a domain to bind, unbind, or restore. Or jump straight to the route-templates library to start composing your own tables.

If you read this far, you’ll probably enjoy the deeper runtime tour in How Workers-native email routing actually processes your inbound mail.

Start here