Receive mail

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:

CapabilityClaims
emailBounce and complaint mail
supportWhatever 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

RuleDelivers
<project>-global-email-bounceBounce and complaint mail
<project>-global-support-inboundYour 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 api

Creating 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.

ESC