You need: a domain whose MX you are willing to point at Cloudflare.
One entry, fanned out
The MX constraint is the decision to take before any of this, provisioning support stands it up, running a support inbox is the customer-mail half, and bounces and suppression is the machine half.
Mail arrives at your app Worker’s single email() entry, which fans it to every capability that handles inbound mail.
Today that is two:
| Capability | Claims |
|---|---|
email | Bounce and complaint mail |
support | Whatever is addressed to a configured support address |
Recipient matching is on the envelope
On the SMTP envelope recipient. Never on a To: header.
Anyone can put your support address in a header on a message routed somewhere else.
The envelope is authority; headers are not — and the same rule applied to the sender is what provenance is about.
Two routing rules, deliberately named apart
| Rule | Delivers |
|---|---|
<project>-global-email-bounce | Bounce and complaint mail |
<project>-global-support-inbound | Your support address |
Idempotency keys on the rule name. Sharing one would make whichever capability provisioned second silently believe its rule already existed.
And the project segment stops two Pithy projects on one zone doing that to each other — with the loser’s mail delivered to the winner’s Worker.
Both are opt-in and explicit
pithy email provision --routing-zone <zone-id> …
pithy support provision --routing-zone <zone-id> --inbound-address support@help.example.com --app-worker apiCreating a rule on the wrong zone would move your real mail, and that is not a mistake a provisioning command gets to make on your behalf.
Leave the flags off and everything else still provisions. Add the rule when you have decided.
Teardown removes the rule first
So mail stops arriving before the Workers that would have handled it go away.
The zone is named on the way out for the same reason it is named on the way in — a rule is addressed through its zone, and the command will not go looking through your domains for one.
What happens to a message support claims
The guard bounds it. It is parsed — multipart and attachments included. The HTML is sanitized through the runtime’s own parser. The raw MIME goes to R2 unchanged. The row lands in D1.
Only then is classification dispatched, as a Workflow — because an inbound handler has a tight CPU budget and a model call does not fit in it.
A model that is slow or briefly down must never take the persistence of somebody’s support request with it.
Threading is on headers
In-Reply-To and References. Never the subject.
Subject matching puts two unrelated people who both wrote “Refund” in one thread, and splits a real conversation the moment a client localizes Re: to Aw:.
Until you configure an address, the inbox is inert
It ignores every message and says so once in the log — rather than quietly storing mail from an address nobody chose.