使用 Cloudflare Workers 中的 email() 处理程序处理入站邮件。这使你可以用自定义逻辑以编程方式处理邮件路由。
将 email 处理函数添加到 Worker 导出的 handlers 中:
export default {
async email(message, env, ctx): Promise<void> {
// Process incoming email
await message.forward("[email protected]");
},
} satisfies ExportedHandler<Env>;from workers import WorkerEntrypoint
class Default(WorkerEntrypoint):
async def email(self, message, env, ctx):
await message.forward("[email protected]")| 参数 | 类型 | 说明 |
|---|---|---|
message |
ForwardableEmailMessage |
入站邮件消息 |
env |
object |
Worker 环境绑定(KV、EMAIL 等) |
ctx |
object |
执行上下文,包含 waitUntil 函数 |
message 参数提供对入站邮件的访问:
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<EmailSendResult>;
reply(message: EmailMessage): Promise<EmailSendResult>;
}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}`);
},
};使用 postal-mime ↗ 解析入站邮件的 MIME 结构。解析器能正确处理 multipart 边界、传输编码和字符集。
import PostalMime from "postal-mime";
export default {
async email(message, env, ctx): Promise<void> {
const email = await PostalMime.parse(message.raw);
console.log(`Subject: ${email.subject}`);
console.log(`Text: ${email.text}`);
console.log(`HTML: ${email.html}`);
},
};将入站邮件转发到已验证的目标地址:
export default {
async email(message, env, ctx): Promise<void> {
// Forward to a single address
await message.forward("[email protected]");
},
};export default {
async email(message, env, ctx): Promise<void> {
const recipient = message.to;
const subject = message.headers.get("subject") || "";
// Route based on recipient
if (recipient.includes("support@")) {
await message.forward("[email protected]");
} else if (recipient.includes("sales@")) {
await message.forward("[email protected]");
} else if (subject.toLowerCase().includes("urgent")) {
await message.forward("[email protected]");
} else {
// Default routing
await message.forward("[email protected]");
}
},
};export default {
async email(message, env, ctx): Promise<void> {
const subject = message.headers.get("subject") || "";
if (subject.toLowerCase().includes("security")) {
// Forward to multiple addresses for security issues
await Promise.all([
message.forward("[email protected]"),
message.forward("[email protected]"),
message.forward("[email protected]"),
]);
} else {
await message.forward("[email protected]");
}
},
};转发时可添加自定义标头。通过 forward() 只能添加带 X- 前缀的标头,其他标头会被移除。
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("[email protected]", customHeaders);
},
};使用 message.reply() 发送自动回复。以此方式构建的回复会与原始消息形成会话线程,并经由同一 SMTP 会话传递,因此会保留原始 Message-ID 链。
通过 Workers API 发送回复必须满足以下要求,否则 reply() 会抛出异常:
- 入站邮件必须具有有效的 DMARC 结果。
- 每个
EmailMessage事件中,一封邮件只能被回复一次。 - 回复中的收件人必须与入站邮件的发件人一致。
- 出站发件人域名必须与接收该邮件的域名一致。
- 若入站邮件的
References标头条目超过 100 个,回复会被拒绝,以防止回复循环和滥用。
回复载荷是由原始 MIME 字符串构建的 EmailMessage。下方示例使用 mimetext ↗ 构建 MIME 正文。mimetext 包需要 nodejs_compat 兼容性标志。
import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";
export default {
async email(message, env, ctx): Promise<void> {
const subject = message.headers.get("subject") || "";
const messageId = message.headers.get("Message-ID");
const reply = createMimeMessage();
if (messageId) {
reply.setHeader("In-Reply-To", messageId);
reply.setHeader("References", messageId);
}
reply.setSender(message.to);
reply.setRecipient(message.from);
reply.setSubject(`Re: ${subject}`);
reply.addMessage({
contentType: "text/plain",
data: "Thank you for your message. We have received your email and will respond shortly.",
});
reply.addMessage({
contentType: "text/html",
data: "<h1>Thank you for your message</h1><p>We have received your email and will respond shortly.</p>",
});
await message.reply(
new EmailMessage(message.to, message.from, reply.asRaw()),
);
// Also forward to human team
await message.forward("[email protected]");
},
};import { EmailMessage } from "cloudflare:email";
import { createMimeMessage } from "mimetext";
export default {
async email(message, env, ctx): Promise<void> {
const sender = message.from;
const recipient = message.to;
const subject = message.headers.get("subject") || "";
const messageId = message.headers.get("Message-ID");
// Don't reply to automated emails
if (
sender.includes("noreply") ||
sender.includes("no-reply") ||
subject.toLowerCase().includes("automated")
) {
await message.forward("[email protected]");
return;
}
// Customized auto-reply based on recipient
let html = "";
if (recipient.includes("support@")) {
html = `
<h1>Support Request Received</h1>
<p>Thank you for contacting support. Your request has been assigned ticket #${Date.now()}.</p>
<p>Expected response time: 2-4 hours during business hours.</p>
`;
} else if (recipient.includes("sales@")) {
html = `
<h1>Sales Inquiry Received</h1>
<p>Thank you for your interest in our products.</p>
<p>A sales representative will contact you within 24 hours.</p>
`;
} else {
html = `
<h1>Message Received</h1>
<p>Thank you for your message. We will respond within 2 business days.</p>
`;
}
const reply = createMimeMessage();
if (messageId) {
reply.setHeader("In-Reply-To", messageId);
reply.setHeader("References", messageId);
}
reply.setSender(recipient);
reply.setRecipient(sender);
reply.setSubject(`Re: ${subject}`);
reply.addMessage({
contentType: "text/plain",
data: html.replace(/<[^>]*>/g, ""),
});
reply.addMessage({ contentType: "text/html", data: html });
await message.reply(new EmailMessage(recipient, sender, reply.asRaw()));
// Forward to appropriate team
await message.forward("[email protected]");
},
};以永久 SMTP 错误拒绝邮件:
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("[email protected]");
},
};export default {
async email(message, env, ctx): Promise<void> {
const subject = message.headers.get("subject") || "";
// Reject based on subject content
const spamKeywords = ["buy now", "limited time", "act fast", "urgent"];
const containsSpam = spamKeywords.some((keyword) =>
subject.toLowerCase().includes(keyword),
);
if (containsSpam) {
message.setReject("Message appears to be spam");
return;
}
// Check message size
if (message.rawSize > 25 * 1024 * 1024) {
// 25 MiB limit (inbound message size)
message.setReject("Message too large");
return;
}
// Continue processing
await message.forward("[email protected]");
},
};在邮件处理中妥善处理错误:
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("[email protected]");
} 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("[email protected]");
} else if (recipient.includes("sales@")) {
await message.forward("[email protected]");
} else {
await message.forward("[email protected]");
}
}- 本地测试:邮件路由开发
- 使用 Email Routing REST API 以编程方式管理规则和地址
- 设置邮件路由配置
- 查看邮件路由示例以了解高级邮件处理
- 了解如何使用 Workers 进行垃圾邮件过滤