Email Workers 处理邮件
上一篇的转发规则只能把邮件原封不动丢到目标邮箱。但很多场景需要更智能的处理,比如根据发件人自动归档、把附件存到对象存储、收到特定关键词触发工单、把邮件内容推到企业 IM。这时候普通的转发规则不够用,得用 Email Workers。它是 Cloudflare 让 Workers(Cloudflare 的边缘计算函数服务)介入邮件处理的能力,让你用代码控制每一封收到的邮件。这篇讲清楚怎么编写、部署、调试 Email Worker。
Email Worker 在链路里的位置
先回顾 Email Routing 的链路,再看清 Worker 插在哪
外部发件服务器
-> Cloudflare MX 接收节点
-> 转发规则匹配
-> [可选] Email Worker 处理
-> 转发到目标邮箱 或 丢弃
当某条规则或 Catch-all 设置为 Send to a Worker 时,邮件不再直接转发,而是先交给指定的 Worker。Worker 拿到邮件后可以读取内容、调用其他服务、决定是否继续转发。
| 操作类型 | 行为 |
|---|---|
| 转发到目标 | 不变,按规则把邮件送到目标邮箱 |
| 丢弃 | 直接丢弃,发件方不会收到退信 |
| 交 Worker 处理 | 把邮件对象交给 Worker,由代码决定下一步 |
Worker 处理完可以显式转发,也可以静默处理不发往任何邮箱,比如只触发一个 webhook 就完事。
Worker 邮件事件
Email Worker 通过 email 事件接收邮件。基础骨架
export default {
async email(message, env, ctx) {
// message 是收到的邮件对象
// env 是绑定环境
// ctx 是请求上下文
},
};
message 对象的关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
| from | string | 发件人地址 |
| to | string | 收件人地址,即你的自定义地址 |
| headers | Headers | 邮件头对象,可读 Subject、Message-ID 等 |
| raw | ReadableStream | 原始 RFC 822 邮件字节流 |
| rawSize | number | 原始邮件字节数 |
| reply | function | 回复邮件的方法 |
| forward | function | 转发到指定地址的方法 |
| setReject | function | 拒收邮件,发件方会收到退信 |
最常用的是 headers 拿主题、from 和 to 判断来源去向、forward 决定是否继续转发。
读取邮件内容
邮件正文有两种读取方式,对应两种使用场景
| 方式 | 适用场景 | 说明 |
|---|---|---|
| message.headers | 只关心主题、发件人等元信息 | 直接读头部,不解析正文 |
| message.raw 流 | 需要正文、附件、HTML 等完整内容 | 要自己解析 MIME |
只看主题简单示例
export default {
async email(message, env, ctx) {
const subject = message.headers.get('subject') || '';
console.log(`收到邮件 来自 ${message.from} 到 ${message.to} 主题 ${subject}`);
},
};
读取完整正文要用 MIME 解析库,因为邮件可能是纯文本、HTML、多部分混合,附件也在流里。 postal-mime 是社区里用得较多的库
npm install postal-mime
解析示例
import { parse } from 'postal-mime';
export default {
async email(message, env, ctx) {
const raw = await new Response(message.raw).arrayBuffer();
const parsed = await parse(raw);
console.log('主题', parsed.subject);
console.log('发件人', parsed.from);
console.log('纯文本正文', parsed.text);
console.log('HTML 正文', parsed.html);
console.log('附件数量', parsed.attachments.length);
for (const att of parsed.attachments) {
console.log('附件', att.filename, '大小', att.content.byteLength);
}
},
};
parsed 对象主要字段
| 字段 | 说明 |
|---|---|
| subject | 主题 |
| from | 发件人对象,含 name 和 address |
| to | 收件人数组 |
| cc | 抄送人数组 |
| text | 纯文本正文,可能为空 |
| html | HTML 正文,可能为空 |
| attachments | 附件数组,每项含 filename、content、mimeType |
触发业务逻辑
拿到邮件内容后就能做任何事。下面是一个完整示例,收到 support@example.com 的邮件就解析正文推送到企业 webhook 并转发到人工邮箱
import { parse } from 'postal-mime';
export default {
async email(message, env, ctx) {
// 只处理 support 地址
if (message.to !== 'support@example.com') {
return;
}
const raw = await new Response(message.raw).arrayBuffer();
const parsed = await parse(raw);
// 推送工单到内部系统
const ticket = {
from: parsed.from.address,
subject: parsed.subject,
text: parsed.text || '',
received_at: new Date().toISOString(),
};
ctx.waitUntil(
fetch(env.TICKET_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(ticket),
})
);
// 同时转发到人工邮箱兜底
await message.forward(env.HUMAN_MAILBOX);
},
};
几个要点
第一,长耗时操作用 ctx.waitUntil 包起来。Worker 的 email 事件有时间限制,但 fetch 这类异步操作可以丢到 waitUntil 里后台执行,主流程不必等它。
第二,forward 是异步方法要 await。它把邮件转发到指定地址,地址必须是已验证的目标地址。
第三,没显式 forward 也没 setReject 时邮件会被静默处理,发件方不知道发生了什么。要么 forward 要么明确 setReject,避免邮件消失无踪。
绑定与部署
Email Worker 不是普通 HTTP Worker,要绑到 Email Routing 才能收邮件。
wrangler.toml 配置
name = "email-handler"
main = "src/index.ts"
compatibility_date = "2024-09-01"
# 邮件事件要显式启用
[triggers]
# 这里不写 cron,邮件事件由 Email Routing 触发
绑定在 Email Routing 控制台做。在转发规则里把操作选 Send to a Worker,下拉选刚部署的 Worker 即可。Catch-all 也能选 Worker。
部署命令
npx wrangler deploy
部署后 Worker 不会立即收到历史邮件,只对新到达的邮件生效。如果想本地测试,wrangler 目前对 email 事件的本地模拟支持有限,最可靠的方式是部署后用真实邮件测试。
常用处理模式
| 模式 | 实现思路 |
|---|---|
| 自动归档 | 解析附件存到 R2(Cloudflare 的对象存储服务),原邮件丢弃 |
| 关键词告警 | 正则匹配正文,命中就推 IM 通知,不命中静默 |
| 自动回复 | 调 message.reply 发确认信,告知用户工单已收到 |
| 智能分发 | 按主题前缀路由到不同人工邮箱,[销售] 给 sales,[技术] 给 dev |
| 垃圾过滤 | 调外部反垃圾 API 判断,可疑的直接 setReject |
| 邮件转任务 | 解析正文构造任务对象写进 D1(Cloudflare 的 SQLite 数据库) |
自动回复示例
export default {
async email(message, env, ctx) {
await message.forward(env.HUMAN_MAILBOX);
// 给发件人回确认信
await message.reply({
to: message.from,
from: message.to,
subject: 'Re: ' + (message.headers.get('subject') || ''),
text: '已收到您的邮件,我们会在 24 小时内回复。',
});
},
};
reply 会用 Cloudflare 的发件能力回信,注意它依然受 Email Routing 不发件的限制,只能回复收到的邮件线程,不能任意发新邮件。
限制与注意
第一,执行时长。email 事件默认有 30 秒处理时间,超过会被中断。长任务用 waitUntil 后台跑,但 waitUntil 也有上限,超长任务要拆成队列异步处理。
第二,不能任意发件。Email Worker 的 reply 和 forward 都基于收到的邮件,不能用来给无关第三方发新邮件。要主动发件请用 Mailchannels 或 Resend 这类服务。
第三,邮件大小。Cloudflare 对单封邮件有大小限制,超大附件可能被截断。建议大附件场景先在 Worker 里判断 rawSize,超阈值就只处理元信息不读全文。
第四,错误处理。任何抛异常都会导致邮件处理失败,但发件方不一定收到退信。建议用 try-catch 包住主逻辑,异常时至少 forward 到一个兜底邮箱,避免邮件丢失。
第五,日志查看。Worker 的 console.log 在 wrangler tail 里能实时看到,部署后开一个终端跑 npx wrangler tail 就能观察邮件处理过程,是调试的主要手段。
小结
Email Worker 把 Email Routing 从静态转发升级为可编程处理。message 对象提供发件人、收件人、headers 和原始邮件流,用 postal-mime 这类库解析 MIME 拿到正文和附件。拿到内容后可以调任意 API、写存储、推通知,再用 forward 或 reply 决定邮件去向。绑定通过 Email Routing 控制台的 Send to a Worker 完成,部署用 wrangler。注意 30 秒执行限制、不能任意发件、错误要兜底转发。这套能力让一个域名邮箱成为业务系统的入口,比单纯转发实用得多。
上一篇 邮件路由基础