Claude-skill-registry cloudflare-email-routing
Cloudflare Email Routing for receiving/sending emails via Workers. Use for email workers, forwarding, allowlists, or encountering Email Trigger errors, worker call failures, SPF issues.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cloudflare-email-routing" ~/.claude/skills/majiayu000-claude-skill-registry-cloudflare-email-routing && rm -rf "$T"
manifest:
skills/data/cloudflare-email-routing/SKILL.mdsource content
Cloudflare Email Routing
Status: Production Ready ✅ | Last Verified: 2025-11-18
What Is Email Routing?
Two capabilities:
- Email Workers - Receive and process incoming emails (allowlists, forwarding, parsing)
- Send Email - Send emails from Workers to verified addresses
Both free and work together for complete email functionality.
Quick Start (10 Minutes)
Part 1: Enable Email Routing
Dashboard setup:
- Dashboard → Domain → Email → Email Routing
- Enable Email Routing → Add records and enable
- Create destination address:
- Custom:
hello@yourdomain.com - Destination: Your email
- Verify via email
- Custom:
- ✅ Basic forwarding active
Part 2: Receiving Emails (Email Workers)
Install dependencies:
bun add postal-mime@2.5.0 mimetext@3.0.27
Create email worker:
// src/email.ts import { EmailMessage } from 'cloudflare:email'; import PostalMime from 'postal-mime'; export default { async email(message, env, ctx) { const parser = new PostalMime.default(); const email = await parser.parse(await new Response(message.raw).arrayBuffer()); console.log('From:', message.from); console.log('Subject:', email.subject); // Forward to destination await message.forward('you@gmail.com'); } };
Configure wrangler.jsonc:
{ "name": "email-worker", "main": "src/email.ts", "compatibility_date": "2025-10-11", "node_compat": true // Required! }
Deploy and connect:
bunx wrangler deploy
Dashboard → Email Workers → Create address → Select worker
Part 3: Sending Emails
Add send email binding:
{ "name": "my-worker", "main": "src/index.ts", "compatibility_date": "2025-10-11", "send_email": [ { "name": "SES", "destination_address": "user@example.com" } ] }
Send from worker:
import { EmailMessage } from 'cloudflare:email'; import { createMimeMessage } from 'mimetext'; const msg = createMimeMessage(); msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' }); msg.setRecipient('user@example.com'); msg.setSubject('Hello!'); msg.addMessage({ contentType: 'text/plain', data: 'Email body here' }); const message = new EmailMessage( 'noreply@yourdomain.com', 'user@example.com', msg.asRaw() ); await env.SES.send(message);
Load
for complete walkthrough.references/setup-guide.md
Critical Rules
Always Do ✅
- Enable node_compat: true for postal-mime
- Verify destination addresses before sending
- Parse with postal-mime for email content
- Use mimetext for creating emails
- Check message.from for allowlists
- Forward with message.forward() (not manual)
- Handle errors (email delivery can fail)
- Test with real emails (not just dashboard)
- Add MX records (automatic via dashboard)
- Log email activity for debugging
Never Do ❌
- Never skip node_compat (postal-mime requires it)
- Never send without verification (delivery fails)
- Never hardcode email addresses in public code
- Never skip parsing (raw email is hard to work with)
- Never ignore spam (implement allowlists/blocklists)
- Never exceed Gmail limits (500 emails/day to Gmail)
- Never skip error handling (emails can fail)
- Never modify DNS manually (use dashboard)
- Never expose email content in logs (PII)
- Never assume instant delivery (email is async)
Common Patterns
Allowlist
const allowlist = ['approved@domain.com']; if (!allowlist.includes(message.from)) { message.setReject('Not on allowlist'); return; } await message.forward('you@gmail.com');
Blocklist
const blocklist = ['spam@bad.com']; if (blocklist.includes(message.from)) { message.setReject('Blocked'); return; } await message.forward('you@gmail.com');
Reply to Email
const msg = createMimeMessage(); msg.setSender({ addr: 'noreply@yourdomain.com' }); msg.setRecipient(message.from); msg.setSubject(`Re: ${email.subject}`); msg.addMessage({ contentType: 'text/plain', data: 'Thanks for your email!' }); const reply = new EmailMessage( 'noreply@yourdomain.com', message.from, msg.asRaw() ); await env.SES.send(reply);
Parse Attachments
const parser = new PostalMime.default(); const email = await parser.parse(await new Response(message.raw).arrayBuffer()); for (const attachment of email.attachments) { console.log('Filename:', attachment.filename); console.log('Type:', attachment.mimeType); console.log('Size:', attachment.content.byteLength); }
Custom Routing Logic
async email(message, env, ctx) { const parser = new PostalMime.default(); const email = await parser.parse(await new Response(message.raw).arrayBuffer()); // Route based on subject if (email.subject.includes('[Support]')) { await message.forward('support@yourdomain.com'); } else if (email.subject.includes('[Sales]')) { await message.forward('sales@yourdomain.com'); } else { await message.forward('general@yourdomain.com'); } }
Email Message Properties
Incoming Messages (ForwardableEmailMessage)
message.from // Sender email message.to // Recipient email message.headers // Email headers message.raw // Raw email stream message.rawSize // Size in bytes // Methods message.forward(address) // Forward to address message.setReject(reason) // Reject email
Parsed Email (PostalMime)
email.from // { name, address } email.to // [{ name, address }] email.subject // Subject line email.text // Plain text body email.html // HTML body email.attachments // Array of attachments email.headers // All headers
Top 5 Errors Prevented
- "Email Trigger not available": Enable node_compat: true
- Destination not verified: Verify all send destinations
- Gmail rate limit: Max 500 emails/day to Gmail
- SPF permerror: Use dashboard to configure DNS
- Worker call failed: Check logs for parsing errors
Use Cases
Use Case 1: Support Ticket System
async email(message, env, ctx) { const parser = new PostalMime.default(); const email = await parser.parse(await new Response(message.raw).arrayBuffer()); // Create ticket in database await env.DB.prepare( 'INSERT INTO tickets (email, subject, body, created_at) VALUES (?, ?, ?, ?)' ).bind(message.from, email.subject, email.text, Date.now()).run(); // Send confirmation const msg = createMimeMessage(); msg.setSender({ addr: 'support@yourdomain.com' }); msg.setRecipient(message.from); msg.setSubject('Ticket Created'); msg.addMessage({ contentType: 'text/plain', data: 'Your support ticket has been created.' }); const confirmation = new EmailMessage( 'support@yourdomain.com', message.from, msg.asRaw() ); await env.SES.send(confirmation); }
Use Case 2: Email Notifications
export default { async fetch(request, env, ctx) { // User signup const { email, name } = await request.json(); const msg = createMimeMessage(); msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' }); msg.setRecipient(email); msg.setSubject('Welcome!'); msg.addMessage({ contentType: 'text/html', data: `<h1>Welcome, ${name}!</h1>` }); const message = new EmailMessage( 'noreply@yourdomain.com', email, msg.asRaw() ); await env.SES.send(message); return new Response('Welcome email sent!'); } };
Use Case 3: Email Forwarding with Filtering
async email(message, env, ctx) { const parser = new PostalMime.default(); const email = await parser.parse(await new Response(message.raw).arrayBuffer()); // Filter spam keywords const spamKeywords = ['viagra', 'lottery', 'prince']; const isSpam = spamKeywords.some(keyword => email.subject.toLowerCase().includes(keyword) || email.text.toLowerCase().includes(keyword) ); if (isSpam) { message.setReject('Spam detected'); return; } await message.forward('you@gmail.com'); }
When to Load References
Load references/setup-guide.md
when:
references/setup-guide.md- First-time Email Routing setup
- Configuring MX records
- Setting up email workers
- Configuring send email binding
- Complete walkthrough needed
Using Bundled Resources
References (
references/):
- Complete setup walkthrough (enabling routing, email workers, send email)setup-guide.md
- All 8 documented errors with solutions and preventioncommon-errors.md
- MX records, SPF, DKIM configuration guidedns-setup.md
- Local testing and development patternslocal-development.md
Templates (
templates/):
- Basic email receiving workerreceive-basic.ts
- Email allowlist implementationreceive-allowlist.ts
- Email blocklist implementationreceive-blocklist.ts
- Auto-reply email workerreceive-reply.ts
- Basic send email examplesend-basic.ts
- Notification email patternsend-notification.ts
- Wrangler configuration for email routingwrangler-email.jsonc
Official Documentation
- Email Routing: https://developers.cloudflare.com/email-routing/
- Email Workers: https://developers.cloudflare.com/email-routing/email-workers/
- Send Email: https://developers.cloudflare.com/email-routing/email-workers/send-email-workers/
Questions? Issues?
- Check
for complete setupreferences/setup-guide.md - Verify node_compat: true in wrangler.jsonc
- Confirm destination addresses verified
- Check logs for errors