**Status**: Production Ready ✅ | **Last Verified**: 2025-11-18
/plugin marketplace add secondsky/claude-skills/plugin install cloudflare-email-routing@claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/common-errors.mdreferences/dns-setup.mdreferences/local-development.mdreferences/setup-guide.mdtemplates/receive-allowlist.tstemplates/receive-basic.tstemplates/receive-blocklist.tstemplates/receive-reply.tstemplates/send-basic.tstemplates/send-notification.tstemplates/wrangler-email.jsoncStatus: Production Ready ✅ | Last Verified: 2025-11-18
Two capabilities:
Both free and work together for complete email functionality.
Dashboard setup:
hello@yourdomain.comInstall 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
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 references/setup-guide.md for complete walkthrough.
const allowlist = ['approved@domain.com'];
if (!allowlist.includes(message.from)) {
message.setReject('Not on allowlist');
return;
}
await message.forward('you@gmail.com');
const blocklist = ['spam@bad.com'];
if (blocklist.includes(message.from)) {
message.setReject('Blocked');
return;
}
await message.forward('you@gmail.com');
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);
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);
}
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');
}
}
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
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
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);
}
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!');
}
};
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');
}
references/setup-guide.md when:References (references/):
setup-guide.md - Complete setup walkthrough (enabling routing, email workers, send email)common-errors.md - All 8 documented errors with solutions and preventiondns-setup.md - MX records, SPF, DKIM configuration guidelocal-development.md - Local testing and development patternsTemplates (templates/):
receive-basic.ts - Basic email receiving workerreceive-allowlist.ts - Email allowlist implementationreceive-blocklist.ts - Email blocklist implementationreceive-reply.ts - Auto-reply email workersend-basic.ts - Basic send email examplesend-notification.ts - Notification email patternwrangler-email.jsonc - Wrangler configuration for email routingQuestions? Issues?
references/setup-guide.md for complete setup