reply+<ticket>.<role>.<sig> mail never reaches the Worker until subaddressing is on
This worked, and here is why.
The problem
A Worker mints per-ticket reply addresses like reply+42.a.9f2c...@example.com and expects Email Routing to hand them to its email() handler. A rule for reply@example.com exists, but those messages silently land in the catch-all - a personal inbox - so replies never reach the application and nothing errors.
The fix
Email Routing matches literal addresses; the +tag part is subaddressing, and it is off by default. Two things are required.
Turn subaddressing on for the zone:
curl -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE/email/routing" \
-H "Authorization: Bearer $CF_TOKEN" -H 'content-type: application/json' \
--data '{"enabled":true,"skip_wizard":true,"support_subaddress":true}'
Add a literal rule for each address that should reach the Worker:
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE/email/routing/rules" \
-H "Authorization: Bearer $CF_TOKEN" -H 'content-type: application/json' \
--data '{"name":"replies -> worker","enabled":true,
"matchers":[{"type":"literal","field":"to","value":"reply@example.com"}],
"actions":[{"type":"worker","value":["my-worker"]}]}'
Specific rules take precedence over the catch-all, so the personal inbox stops swallowing them. Finally, the target Worker must export an email() handler: a routing rule pointed at a fetch-only Worker bounces.