A Cloudflare Email Worker that forwards incoming emails to multiple target addresses based on recipient with automatic failover to R2 storage and Discord webhook alerts.
- ✅ Routes emails to different addresses based on recipient (configurable via environment variables)
- ✅ Automatic backup to R2 bucket when delivery fails
- ✅ Discord webhook alerts for failed deliveries
- ✅ Comprehensive error handling and logging
- ✅ Full test coverage
- Cloudflare account with Workers and R2 enabled
- Discord webhook URL (optional but recommended)
- Wrangler CLI installed
Create the R2 bucket for email storage:
wrangler r2 bucket create fail-safe-mail-storageUpdate the email routing configuration in wrangler.jsonc:
"vars": {
"EMAIL_ROUTING": {
"user1@yourdomain.com": "user1@personal.com",
"user2@yourdomain.com": "user2@personal.com",
"@yourdomain.com": "catchall@personal.com",
"@default": "fallback@personal.com"
}
}You can add as many routing rules as needed. The format supports:
- Exact matches:
"recipient@domain.com": "target@domain.com" - Catch-all routing:
"@domain.com": "target@domain.com"(for any email at that domain) - Global default:
"@default": "target@domain.com"(for any email that doesn't match other rules)
Example with multiple domains:
"EMAIL_ROUTING": {
"admin@company.com": "admin@personal.com",
"support@company.com": "support@personal.com",
"@company.com": "catchall@personal.com",
"@anotherdomain.com": "another@personal.com",
"@default": "fallback@personal.com"
}Set the Discord webhook URL as a secret (recommended for security):
wrangler secret put DISCORD_WEBHOOK_URLWhen prompted, enter your Discord webhook URL:
https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN
npm run deploy- Go to Cloudflare Dashboard → Email Routing
- Add your domain
- Configure the worker to handle incoming emails
- Set up email forwarding rules
- Email Reception: All incoming emails are received by the worker
- Routing: The worker checks the recipient email against the
EMAIL_ROUTINGconfiguration:- First tries exact match (e.g.,
support@yourdomain.com) - If no exact match, tries catch-all pattern (e.g.,
@yourdomain.com) - If still no match, uses global default (
@default)
- First tries exact match (e.g.,
- Forwarding: Emails are forwarded to the appropriate target address based on the routing rules
- Error Handling: If forwarding fails or no routing rule exists:
- Email is saved to R2 bucket with metadata
- Discord alert is sent with error details
- Error is logged for debugging
Failed emails are stored in R2 with:
- Filename:
email-backup-{timestamp}-{sanitized_from}.eml - Content-Type:
message/rfc822 - Metadata: from, to, subject, timestamp, originalRecipient, targetEmail
Discord alerts include:
- 🚨 Alert title and description
- Email details (from, original recipient, target email, subject)
- Error message
- Timestamp
- Backup confirmation or routing error status
Run the test suite:
npm testTests cover:
- Successful email forwarding
- R2 backup on failure
- Discord alerting
- Error handling scenarios
Start local development:
npm run devYou can test the email worker locally using the provided sample.eml file. The worker will be available at http://127.0.0.1:8787.
curl.exe -v -X POST `
"http://127.0.0.1:8787/cdn-cgi/handler/email?from=sender@example.com&to=user1@personal.com" `
-H "Content-Type: message/rfc822" `
--data-binary "@sample.eml"curl -v -X POST \
"http://127.0.0.1:8787/cdn-cgi/handler/email?from=sender@example.com&to=user1@personal.com" \
-H "Content-Type: message/rfc822" \
--data-binary "@sample.eml"This will:
- Send the sample email to your local worker
- Attempt to forward it
- If forwarding fails (which it will in local dev), save it to R2 and send a Discord alert
- Show detailed logs in your terminal
Note: In local development, email forwarding will fail since you're not connected to a real SMTP server, but this allows you to test the R2 backup and Discord alerting functionality.
To configure email routing, update the EMAIL_ROUTING environment variable in wrangler.jsonc:
"vars": {
"EMAIL_ROUTING": {
"recipient@domain.com": "target@domain.com",
"@domain.com": "catchall@domain.com",
"@default": "fallback@domain.com"
}
}You can add multiple routing rules with exact matches, catch-all patterns, and global default:
"vars": {
"EMAIL_ROUTING": {
"user1@yourdomain.com": "user1@personal.com",
"user2@yourdomain.com": "user2@personal.com",
"@yourdomain.com": "catchall@personal.com",
"@default": "fallback@personal.com"
}
}Update the bucket name in wrangler.jsonc:
"r2_buckets": [
{
"binding": "EMAIL_STORAGE",
"bucket_name": "your-bucket-name"
}
]- Check Cloudflare Workers logs for email processing status
- Monitor R2 bucket for failed email backups
- Discord alerts provide real-time failure notifications
-
Emails not being forwarded
- Check email routing configuration
- Verify worker is deployed and active
- Check worker logs for errors
-
R2 backup not working
- Verify R2 bucket exists and is accessible
- Check bucket permissions
- Review worker logs for R2 errors
-
Discord alerts not sending
- Verify webhook URL is correct
- Check Discord webhook permissions
- Review worker logs for fetch errors
Enable detailed logging by checking the Cloudflare Workers dashboard logs or using:
wrangler tail