cloudflare-email-routing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare Email Routing

Cloudflare Email Routing

Status: Production Ready ✅ | Last Verified: 2025-11-18

状态: 生产就绪 ✅ | 最后验证日期: 2025-11-18

What Is Email Routing?

什么是Email Routing?

Two capabilities:
  1. Email Workers - Receive and process incoming emails (allowlists, forwarding, parsing)
  2. Send Email - Send emails from Workers to verified addresses
Both free and work together for complete email functionality.

包含两大功能:
  1. Email Workers - 接收并处理邮件(白名单、转发、解析)
  2. 发送邮件 - 通过Workers向已验证地址发送邮件
两项功能均免费,可配合使用以实现完整的邮件功能。

Quick Start (10 Minutes)

快速入门(10分钟)

Part 1: Enable Email Routing

第一部分:启用Email Routing

Dashboard setup:
  1. Dashboard → Domain → EmailEmail Routing
  2. Enable Email RoutingAdd records and enable
  3. Create destination address:
    • Custom:
      hello@yourdomain.com
    • Destination: Your email
    • Verify via email
  4. ✅ Basic forwarding active
控制台设置:
  1. 控制台 → 域名 → 邮件Email Routing
  2. 启用Email Routing添加记录并启用
  3. 创建目标地址:
    • 自定义地址:
      hello@yourdomain.com
    • 目标地址: 你的邮箱
    • 通过邮件验证
  4. ✅ 基础转发功能已激活

Part 2: Receiving Emails (Email Workers)

第二部分:接收邮件(Email Workers)

Install dependencies:
bash
bun add postal-mime@2.5.0 mimetext@3.0.27
Create email worker:
typescript
// src/email.ts
import { EmailMessage } from 'cloudflare:email';
import PostalMime from 'postal-mime';

export default {
  async email(message, env, ctx) {
    const parser = new PostalMime.default();
    const email = await parser.parse(await new Response(message.raw).arrayBuffer());

    console.log('From:', message.from);
    console.log('Subject:', email.subject);

    // Forward to destination
    await message.forward('you@gmail.com');
  }
};
Configure wrangler.jsonc:
jsonc
{
  "name": "email-worker",
  "main": "src/email.ts",
  "compatibility_date": "2025-10-11",
  "node_compat": true  // Required!
}
Deploy and connect:
bash
bunx wrangler deploy
Dashboard → Email Workers → Create address → Select worker
安装依赖:
bash
bun add postal-mime@2.5.0 mimetext@3.0.27
创建邮件Worker:
typescript
// src/email.ts
import { EmailMessage } from 'cloudflare:email';
import PostalMime from 'postal-mime';

export default {
  async email(message, env, ctx) {
    const parser = new PostalMime.default();
    const email = await parser.parse(await new Response(message.raw).arrayBuffer());

    console.log('发件人:', message.from);
    console.log('主题:', email.subject);

    // 转发至目标地址
    await message.forward('you@gmail.com');
  }
};
配置wrangler.jsonc:
jsonc
{
  "name": "email-worker",
  "main": "src/email.ts",
  "compatibility_date": "2025-10-11",
  "node_compat": true  // 必须配置!
}
部署并关联:
bash
bunx wrangler deploy
控制台 → Email Workers → 创建地址 → 选择Worker

Part 3: Sending Emails

第三部分:发送邮件

Add send email binding:
jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-11",
  "send_email": [
    {
      "name": "SES",
      "destination_address": "user@example.com"
    }
  ]
}
Send from worker:
typescript
import { EmailMessage } from 'cloudflare:email';
import { createMimeMessage } from 'mimetext';

const msg = createMimeMessage();
msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' });
msg.setRecipient('user@example.com');
msg.setSubject('Hello!');
msg.addMessage({
  contentType: 'text/plain',
  data: 'Email body here'
});

const message = new EmailMessage(
  'noreply@yourdomain.com',
  'user@example.com',
  msg.asRaw()
);

await env.SES.send(message);
Load
references/setup-guide.md
for complete walkthrough.

添加发送邮件绑定:
jsonc
{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2025-10-11",
  "send_email": [
    {
      "name": "SES",
      "destination_address": "user@example.com"
    }
  ]
}
通过Worker发送邮件:
typescript
import { EmailMessage } from 'cloudflare:email';
import { createMimeMessage } from 'mimetext';

const msg = createMimeMessage();
msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' });
msg.setRecipient('user@example.com');
msg.setSubject('Hello!');
msg.addMessage({
  contentType: 'text/plain',
  data: 'Email body here'
});

const message = new EmailMessage(
  'noreply@yourdomain.com',
  'user@example.com',
  msg.asRaw()
);

await env.SES.send(message);
查阅
references/setup-guide.md
获取完整操作流程。

Critical Rules

重要规则

Always Do ✅

必须遵守 ✅

  1. Enable node_compat: true for postal-mime
  2. Verify destination addresses before sending
  3. Parse with postal-mime for email content
  4. Use mimetext for creating emails
  5. Check message.from for allowlists
  6. Forward with message.forward() (not manual)
  7. Handle errors (email delivery can fail)
  8. Test with real emails (not just dashboard)
  9. Add MX records (automatic via dashboard)
  10. Log email activity for debugging
  1. 启用node_compat: true 以支持postal-mime
  2. 发送前验证目标地址
  3. 使用postal-mime解析邮件内容
  4. 使用mimetext创建邮件
  5. 检查message.from以配置白名单
  6. 使用message.forward()转发邮件(不要手动转发)
  7. 处理错误(邮件投递可能失败)
  8. 使用真实邮件测试(不要仅依赖控制台)
  9. 添加MX记录(控制台会自动配置)
  10. 记录邮件活动以方便调试

Never Do ❌

切勿操作 ❌

  1. Never skip node_compat (postal-mime requires it)
  2. Never send without verification (delivery fails)
  3. Never hardcode email addresses in public code
  4. Never skip parsing (raw email is hard to work with)
  5. Never ignore spam (implement allowlists/blocklists)
  6. Never exceed Gmail limits (500 emails/day to Gmail)
  7. Never skip error handling (emails can fail)
  8. Never modify DNS manually (use dashboard)
  9. Never expose email content in logs (PII)
  10. Never assume instant delivery (email is async)

  1. 切勿跳过node_compat配置(postal-mime依赖该配置)
  2. 切勿向未验证地址发送邮件(投递会失败)
  3. 切勿在公开代码中硬编码邮箱地址
  4. 切勿跳过解析步骤(原始邮件难以处理)
  5. 切勿忽略垃圾邮件(实现白名单/黑名单)
  6. 切勿超出Gmail限制(每日向Gmail发送邮件最多500封)
  7. 切勿跳过错误处理(邮件可能投递失败)
  8. 切勿手动修改DNS(使用控制台操作)
  9. 切勿在日志中暴露邮件内容(属于个人可识别信息PII)
  10. 切勿假设邮件即时投递(邮件是异步的)

Common Patterns

常见模式

Allowlist

白名单

typescript
const allowlist = ['approved@domain.com'];

if (!allowlist.includes(message.from)) {
  message.setReject('Not on allowlist');
  return;
}

await message.forward('you@gmail.com');
typescript
const allowlist = ['approved@domain.com'];

if (!allowlist.includes(message.from)) {
  message.setReject('不在白名单内');
  return;
}

await message.forward('you@gmail.com');

Blocklist

黑名单

typescript
const blocklist = ['spam@bad.com'];

if (blocklist.includes(message.from)) {
  message.setReject('Blocked');
  return;
}

await message.forward('you@gmail.com');
typescript
const blocklist = ['spam@bad.com'];

if (blocklist.includes(message.from)) {
  message.setReject('已被拉黑');
  return;
}

await message.forward('you@gmail.com');

Reply to Email

回复邮件

typescript
const msg = createMimeMessage();
msg.setSender({ addr: 'noreply@yourdomain.com' });
msg.setRecipient(message.from);
msg.setSubject(`Re: ${email.subject}`);
msg.addMessage({
  contentType: 'text/plain',
  data: 'Thanks for your email!'
});

const reply = new EmailMessage(
  'noreply@yourdomain.com',
  message.from,
  msg.asRaw()
);

await env.SES.send(reply);
typescript
const msg = createMimeMessage();
msg.setSender({ addr: 'noreply@yourdomain.com' });
msg.setRecipient(message.from);
msg.setSubject(`Re: ${email.subject}`);
msg.addMessage({
  contentType: 'text/plain',
  data: '感谢您的来信!'
});

const reply = new EmailMessage(
  'noreply@yourdomain.com',
  message.from,
  msg.asRaw()
);

await env.SES.send(reply);

Parse Attachments

解析附件

typescript
const parser = new PostalMime.default();
const email = await parser.parse(await new Response(message.raw).arrayBuffer());

for (const attachment of email.attachments) {
  console.log('Filename:', attachment.filename);
  console.log('Type:', attachment.mimeType);
  console.log('Size:', attachment.content.byteLength);
}
typescript
const parser = new PostalMime.default();
const email = await parser.parse(await new Response(message.raw).arrayBuffer());

for (const attachment of email.attachments) {
  console.log('文件名:', attachment.filename);
  console.log('类型:', attachment.mimeType);
  console.log('大小:', attachment.content.byteLength);
}

Custom Routing Logic

自定义路由逻辑

typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // Route based on subject
  if (email.subject.includes('[Support]')) {
    await message.forward('support@yourdomain.com');
  } else if (email.subject.includes('[Sales]')) {
    await message.forward('sales@yourdomain.com');
  } else {
    await message.forward('general@yourdomain.com');
  }
}

typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // 根据主题路由
  if (email.subject.includes('[Support]')) {
    await message.forward('support@yourdomain.com');
  } else if (email.subject.includes('[Sales]')) {
    await message.forward('sales@yourdomain.com');
  } else {
    await message.forward('general@yourdomain.com');
  }
}

Email Message Properties

邮件消息属性

Incoming Messages (ForwardableEmailMessage)

收件消息(ForwardableEmailMessage)

typescript
message.from        // Sender email
message.to          // Recipient email
message.headers     // Email headers
message.raw         // Raw email stream
message.rawSize     // Size in bytes

// Methods
message.forward(address)        // Forward to address
message.setReject(reason)       // Reject email
typescript
message.from        // 发件人邮箱
message.to          // 收件人邮箱
message.headers     // 邮件头
message.raw         // 原始邮件流
message.rawSize     // 字节大小

// 方法
message.forward(address)        // 转发至指定地址
message.setReject(reason)       // 拒绝邮件

Parsed Email (PostalMime)

解析后的邮件(PostalMime)

typescript
email.from          // { name, address }
email.to            // [{ name, address }]
email.subject       // Subject line
email.text          // Plain text body
email.html          // HTML body
email.attachments   // Array of attachments
email.headers       // All headers

typescript
email.from          // { name, address }
email.to            // [{ name, address }]
email.subject       // 主题
email.text          // 纯文本正文
email.html          // HTML正文
email.attachments   // 附件数组
email.headers       // 所有邮件头

Top 5 Errors Prevented

可预防的五大常见错误

  1. "Email Trigger not available": Enable node_compat: true
  2. Destination not verified: Verify all send destinations
  3. Gmail rate limit: Max 500 emails/day to Gmail
  4. SPF permerror: Use dashboard to configure DNS
  5. Worker call failed: Check logs for parsing errors

  1. "Email Trigger不可用": 启用node_compat: true
  2. 目标地址未验证: 验证所有发送目标地址
  3. Gmail速率限制: 每日向Gmail发送邮件最多500封
  4. SPF永久错误: 使用控制台配置DNS
  5. Worker调用失败: 检查日志排查解析错误

Use Cases

应用场景

Use Case 1: Support Ticket System

场景1:客服工单系统

typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // Create ticket in database
  await env.DB.prepare(
    'INSERT INTO tickets (email, subject, body, created_at) VALUES (?, ?, ?, ?)'
  ).bind(message.from, email.subject, email.text, Date.now()).run();

  // Send confirmation
  const msg = createMimeMessage();
  msg.setSender({ addr: 'support@yourdomain.com' });
  msg.setRecipient(message.from);
  msg.setSubject('Ticket Created');
  msg.addMessage({
    contentType: 'text/plain',
    data: 'Your support ticket has been created.'
  });

  const confirmation = new EmailMessage(
    'support@yourdomain.com',
    message.from,
    msg.asRaw()
  );

  await env.SES.send(confirmation);
}
typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // 在数据库中创建工单
  await env.DB.prepare(
    'INSERT INTO tickets (email, subject, body, created_at) VALUES (?, ?, ?, ?)'
  ).bind(message.from, email.subject, email.text, Date.now()).run();

  // 发送确认邮件
  const msg = createMimeMessage();
  msg.setSender({ addr: 'support@yourdomain.com' });
  msg.setRecipient(message.from);
  msg.setSubject('工单已创建');
  msg.addMessage({
    contentType: 'text/plain',
    data: '您的客服工单已创建。'
  });

  const confirmation = new EmailMessage(
    'support@yourdomain.com',
    message.from,
    msg.asRaw()
  );

  await env.SES.send(confirmation);
}

Use Case 2: Email Notifications

场景2:邮件通知

typescript
export default {
  async fetch(request, env, ctx) {
    // User signup
    const { email, name } = await request.json();

    const msg = createMimeMessage();
    msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' });
    msg.setRecipient(email);
    msg.setSubject('Welcome!');
    msg.addMessage({
      contentType: 'text/html',
      data: `<h1>Welcome, ${name}!</h1>`
    });

    const message = new EmailMessage(
      'noreply@yourdomain.com',
      email,
      msg.asRaw()
    );

    await env.SES.send(message);

    return new Response('Welcome email sent!');
  }
};
typescript
export default {
  async fetch(request, env, ctx) {
    // 用户注册
    const { email, name } = await request.json();

    const msg = createMimeMessage();
    msg.setSender({ name: 'App', addr: 'noreply@yourdomain.com' });
    msg.setRecipient(email);
    msg.setSubject('欢迎!');
    msg.addMessage({
      contentType: 'text/html',
      data: `<h1>Welcome, ${name}!</h1>`
    });

    const message = new EmailMessage(
      'noreply@yourdomain.com',
      email,
      msg.asRaw()
    );

    await env.SES.send(message);

    return new Response('欢迎邮件已发送!');
  }
};

Use Case 3: Email Forwarding with Filtering

场景3:带过滤功能的邮件转发

typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // Filter spam keywords
  const spamKeywords = ['viagra', 'lottery', 'prince'];
  const isSpam = spamKeywords.some(keyword =>
    email.subject.toLowerCase().includes(keyword) ||
    email.text.toLowerCase().includes(keyword)
  );

  if (isSpam) {
    message.setReject('Spam detected');
    return;
  }

  await message.forward('you@gmail.com');
}

typescript
async email(message, env, ctx) {
  const parser = new PostalMime.default();
  const email = await parser.parse(await new Response(message.raw).arrayBuffer());

  // 过滤垃圾邮件关键词
  const spamKeywords = ['viagra', 'lottery', 'prince'];
  const isSpam = spamKeywords.some(keyword =>
    email.subject.toLowerCase().includes(keyword) ||
    email.text.toLowerCase().includes(keyword)
  );

  if (isSpam) {
    message.setReject('检测到垃圾邮件');
    return;
  }

  await message.forward('you@gmail.com');
}

When to Load References

何时查阅参考文档

Load
references/setup-guide.md
when:

当以下情况时查阅
references/setup-guide.md
:

  • First-time Email Routing setup
  • Configuring MX records
  • Setting up email workers
  • Configuring send email binding
  • Complete walkthrough needed

  • 首次配置Email Routing
  • 配置MX记录
  • 设置Email Workers
  • 配置发送邮件绑定
  • 需要完整操作流程

Using Bundled Resources

使用捆绑资源

References (
references/
):
  • setup-guide.md
    - Complete setup walkthrough (enabling routing, email workers, send email)
  • common-errors.md
    - All 8 documented errors with solutions and prevention
  • dns-setup.md
    - MX records, SPF, DKIM configuration guide
  • local-development.md
    - Local testing and development patterns
Templates (
templates/
):
  • receive-basic.ts
    - Basic email receiving worker
  • receive-allowlist.ts
    - Email allowlist implementation
  • receive-blocklist.ts
    - Email blocklist implementation
  • receive-reply.ts
    - Auto-reply email worker
  • send-basic.ts
    - Basic send email example
  • send-notification.ts
    - Notification email pattern
  • wrangler-email.jsonc
    - Wrangler configuration for email routing

参考文档 (
references/
):
  • setup-guide.md
    - 完整设置流程(启用路由、Email Workers、发送邮件)
  • common-errors.md
    - 8种已记录错误的解决方案与预防措施
  • dns-setup.md
    - MX记录、SPF、DKIM配置指南
  • local-development.md
    - 本地测试与开发模式
模板 (
templates/
):
  • receive-basic.ts
    - 基础邮件接收Worker
  • receive-allowlist.ts
    - 邮件白名单实现
  • receive-blocklist.ts
    - 邮件黑名单实现
  • receive-reply.ts
    - 自动回复邮件Worker
  • send-basic.ts
    - 基础发送邮件示例
  • send-notification.ts
    - 通知邮件模式
  • wrangler-email.jsonc
    - Email Routing的Wrangler配置

Official Documentation

官方文档


Questions? Issues?
  1. Check
    references/setup-guide.md
    for complete setup
  2. Verify node_compat: true in wrangler.jsonc
  3. Confirm destination addresses verified
  4. Check logs for errors

有疑问?遇到问题?
  1. 查阅
    references/setup-guide.md
    获取完整设置流程
  2. 验证wrangler.jsonc中是否启用node_compat: true
  3. 确认目标地址已验证
  4. 检查日志排查错误