Cloudflare Email Routing — inbound email processing in Workers + MailChannels outbound.
Email Routing lets you process inbound email inside a Worker via the email event handler. MailChannels integration enables outbound transactional email — no separate ESP required.
Free on any Cloudflare-managed domain.
[[email]]
type = "receive"
name = "email-handler"
destination_address = "worker" # routes all matched email to your Worker
import { EmailMessage } from 'cloudflare:email';
import { createMimeMessage } from 'mimetext';
export default {
async email(message: EmailMessage, env: Env, ctx: ExecutionContext) {
// Reject spam before processing
if (message.headers.get('x-spam-flag') === 'YES') {
message.setReject('Spam rejected');
return;
}
// Read raw MIME content
const rawEmail = await new Response(message.raw).text();
// Forward to another address
await message.forward('penny@2nth.ai');
// Or trigger an AI workflow with the email body
await env.QUEUE.send({
type: 'email_intake',
from: message.from,
subject: message.headers.get('subject'),
body: rawEmail,
});
}
};
async function sendEmail(params: {
to: string;
subject: string;
html: string;
from?: string;
}, env: Env): Promise<void> {
const response = await fetch('https://api.mailchannels.net/tx/v1/send', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
personalizations: [{ to: [{ email: params.to }] }],
from: { email: params.from ?? 'penny@2nth.ai', name: 'Penny — 2nth.ai' },
subject: params.subject,
content: [{ type: 'text/html', value: params.html }],
}),
});
if (!response.ok) {
throw new Error(`MailChannels error: ${response.status}`);
}
}
include:relay.mailchannels.net to your SPF record and set up DKIM, or your email will be rejected.message.raw is a ReadableStream: Wrap in new Response(message.raw).text() to read it.