六维教程

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 秒执行限制、不能任意发件、错误要兜底转发。这套能力让一个域名邮箱成为业务系统的入口,比单纯转发实用得多。

上一篇 邮件路由基础

上一篇
邮件路由基础
下一篇
Cloudflare Hyperdrive 连接池基础概念