Introduction
Cloudflare Email Routing is an awesome service for routing emails from custom domain email addresses to another address. However, it has one drawback, should an email be failed to forward, that message is gone with no way to retrieve without asking the sender to send again.
The failure can happen for reasons such as the one I encountered recently when forwarding to my Outlook address:
Error: could not initialize upstream: Unknown error: permanent error (550): 5.7.1 Unfortunately, messages from [104.30.10.94] weren't sent. Please contact your Internet service provider since part of their network is on our block list (S3150). You can also refer your provider to http://mail.live.com/mail/troubleshooting.aspx#errors. [Name=Protocol Filter Agent][AGT=PFA][MxId=11BBBE2FCFF073D1] [SJ1PEPF000023D4.namprd21.prod.outlook.com 2025-08-22T10:07:27.756Z 08DDDF17EB1E43D9]Therefore, it is reasonable to have some fallback setup that will resolve such issues.
Implementation
To resolve the above issues, I’ve setup an Email Worker that will:
- Try to forward the incoming email to a specified list of destination addresses until one goes through.
- If forwarding fails, save the original message to a Cloudflare R2 bucket in
.emlformat.
It has some configurable parameters such as:
- Forwarding strategy: Controlled by environment variable
FORWARD_STRATEGY:failover: stop on first successful forward (default)all: forward to all destinations
- Copy retention policy: Controlled by
KEEP_COPY:always: save every email to R2never: don’t save copiesundeliverable-only: only save if all forwards fail (as per their strategy) (default)
Installation guide
The full installation guide is available in the GitHub repository, but here’s the quick version:
- Set up Cloudflare Email Routing for your domain.
- Create an R2 bucket to store backup emails.
- Clone the repository:
git clone https://github.com/azraftaohid/email-router - Bind the R2 bucket in
wrangler.jsonc. - Set environment variables (
FORWARD_ADDRESS,FORWARD_STRATEGY,KEEP_COPY). - Deploy with Wrangler:
yarn deploy - Point Email Routing rules to this Worker.
Simple enough, all incoming emails should now follow this defined strategy. You can test this by sending a test email to your custom domain email and check the logs by going to Cloudflare Dashboard > Workers > email-router > Logs.
Pricing
While Cloudflare Email Routing is free of cost, Cloudflare Workers (the underlying service that enables Email Workers) can incur costs. But it has generous usage on free tier: 100,000 per day for free as of writing, meaning you can essentially receive that many emails without incurring any charges (conservatively, consider the free limit to be 80,000, which is still a lot).
Conclusion
Cloudflare Email Routing is powerful, but it comes with the risk of silent data loss when a destination rejects messages. By combining it with R2 storage, you get the best of both worlds. If you encounter any issues with this setup, or has some questions, feel free to get in touch.
What’s next
In the future, I might add the option to send a Telegram message notification whenever delivery fails. This would give an instant alert alongside saving the email copy in R2, making it even easier to react quickly to missed messages.