Skip to content

Workers API

Process incoming emails with the email() handler in Cloudflare Workers. Forward, reply, reject, or process emails programmatically.

Process incoming emails using the email() handler in your Cloudflare Workers. This allows you to programmatically handle email routing with custom logic.

Email handler syntax

Add the email handler function to your Worker's exported handlers:

TypeScript
interface Env {
EMAIL: SendEmail;
}
export default {
async email(message, env, ctx): Promise<void> {
// Process incoming email
await message.forward("destination@example.com");
},
} satisfies ExportedHandler<Env>;

Parameters

ParameterTypeDescription
messageForwardableEmailMessageThe incoming email message
envobjectWorker environment bindings (KV, EMAIL, etc.)
ctxobjectExecution context with waitUntil function

ForwardableEmailMessage interface

The message parameter provides access to the incoming email:

TypeScript
interface ForwardableEmailMessage {
readonly from: string; // Sender email address (envelope MAIL FROM)
readonly to: string; // Recipient email address (envelope RCPT TO)
readonly headers: Headers; // Email headers (Subject, Message-ID, etc.)
readonly raw: ReadableStream; // Raw MIME email content stream
readonly rawSize: number; // Size of raw email in bytes
readonly canBeForwarded: boolean; // Whether the message can be forwarded
// Actions
setReject(reason: string): void;
forward(rcptTo: string, headers?: Headers): Promise<void>;
reply(message: EmailMessage): Promise<void>;
}

Properties

TypeScript
export default {
async email(message, env, ctx): Promise<void> {
// Access email metadata
console.log(`From: ${message.from}`);
console.log(`To: ${message.to}`);
console.log(`Size: ${message.rawSize} bytes`);
// Access headers
const subject = message.headers.get("subject");
const date = message.headers.get("date");
const messageId = message.headers.get("message-id");
console.log(`Subject: ${subject}`);
console.log(`Date: ${date}`);
console.log(`Message-ID: ${messageId}`);
},
};

Email actions

Forward emails

Forward incoming emails to verified destination addresses:

TypeScript
export default {
async email(message, env, ctx): Promise<void> {
// Forward to a single address
await message.forward("team@company.com");
},
};

Forward with custom headers

Add custom headers when forwarding:

TypeScript
export default {
async email(message, env, ctx): Promise<void> {
// Create custom headers
const customHeaders = new Headers();
customHeaders.set("X-Processed-By", "Email-Worker");
customHeaders.set("X-Processing-Time", new Date().toISOString());
customHeaders.set("X-Original-Recipient", message.to);
customHeaders.set("X-Spam-Score", "0.1"); // Example spam score
// Forward with custom headers
await message.forward("processed@company.com", customHeaders);
},
};

Reply to emails

Send automatic replies using the Email Service binding:

TypeScript
interface Env {
EMAIL: SendEmail;
}
export default {
async email(message, env, ctx): Promise<void> {
const subject = message.headers.get("subject") || "";
// Send auto-reply
await env.EMAIL.send({
to: message.from,
from: message.to, // Reply from the original recipient address
subject: `Re: ${subject}`,
html: `
<h1>Thank you for your message</h1>
<p>We have received your email and will respond shortly.</p>
<p>Original message received at: ${new Date().toISOString()}</p>
`,
text: "Thank you for your message. We have received your email and will respond shortly.",
});
// Also forward to human team
await message.forward("team@company.com");
},
};

Reject emails

Reject emails with a permanent SMTP error:

TypeScript
export default {
async email(message, env, ctx): Promise<void> {
const sender = message.from;
// Block specific senders
const blockedDomains = ["spam.com", "unwanted.net"];
const senderDomain = sender.split("@")[1];
if (blockedDomains.includes(senderDomain)) {
message.setReject("Sender domain not allowed");
return;
}
// Continue processing
await message.forward("inbox@company.com");
},
};

Error handling

Handle errors gracefully in email processing:

TypeScript
export default {
async email(message, env, ctx): Promise<void> {
try {
// Main email processing logic
await processEmail(message, env);
} catch (error) {
console.error("Email processing failed:", error);
// Log error for monitoring
if (env.ERROR_LOGS) {
await env.ERROR_LOGS.put(
`error-${Date.now()}`,
JSON.stringify({
error: error.message,
stack: error.stack,
from: message.from,
to: message.to,
timestamp: new Date().toISOString(),
}),
);
}
// Fallback: forward to admin
try {
await message.forward("admin@company.com");
} catch (fallbackError) {
console.error("Fallback forwarding failed:", fallbackError);
// Last resort: reject the email
message.setReject("Internal processing error");
}
}
},
};
async function processEmail(message, env) {
// Your main email processing logic here
const recipient = message.to;
if (recipient.includes("support@")) {
await message.forward("support@company.com");
} else if (recipient.includes("sales@")) {
await message.forward("sales@company.com");
} else {
await message.forward("general@company.com");
}
}

Next steps: