malipetek

← Knowledgebase

Solved

reply+<ticket>.<role>.<sig> mail never reaches the Worker until subaddressing is on

cloudflare email-routing workers subaddressing

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.