Cloudflare 邮箱路由:自定义域名收信与转发

用 Cloudflare Email Routing 把任意 @你的域名 转到真实邮箱,再 IMAP 拉取解析

Cloudflare 邮箱路由

Cloudflare 不给你一个真正的邮箱账号。它做的事情更简单:域名的 MX 指到 Cloudflare,别人把信打到 随便什么前缀@你的域名,Cloudflare 按规则 转发 到你已经验证过的真实邮箱(Gmail、QQ 企业邮、Outlook 都行)。

所以整条链路是:

1
2
3
4
5
发件人
  → MX 解析到 Cloudflare
  → Email Routing 规则匹配
  → 转发到 Destination(真实邮箱)
  → 你再用 IMAP / 网页邮箱去读

业务上很常见的用法:给每个客户、每个职位、每个表单发一个独一无二的地址,比如 job-1024@shuimo0413.top。对外看起来像公司邮箱,背后其实都进同一个收件箱,后面用脚本按「寄到谁」把邮件归到对应客户。


为什么不用每人开一个真实邮箱

真实邮箱(腾讯企业邮、Google Workspace)要开账号、占席位、付钱、管密码。简历、客服、落地页这种场景,地址可能上百个,账号方案扛不住。

邮箱路由的取舍:

真实邮箱(每人一个) Cloudflare 路由
本质 有独立账号、密码、容量 只有转发规则,没有收件箱
成本 按席位 路由本身免费
地址数量 一个账号一个地址 Catch-all 几乎随便造地址
怎么读信 各登各的 所有信进同一个 Destination,IMAP 一次拉完
限制 席位、容量 自定义规则 200 条、Destination 200 个、单封 25 MiB

简历场景要的是「地址多、账号少」,走路由更合适。


先搞清楚三个名词

1. Destination Address(真实收件箱)

信最终要落到的地方,必须是一个 已经存在、能点验证邮件 的邮箱。Cloudflare 会给这个地址发一封验证信,点过链接才允许当转发目标。

一个 Cloudflare 账号大约能验证 200 个 Destination,多个域名可以共用。

2. Custom Address(自定义地址)

一条明确规则,例如:

  • admin@shuimo0413.top → 转到 Destination A
  • sales@shuimo0413.top → 转到 Destination B

匹配方式是字面量 to,写谁就只收谁。自定义规则每个域名大约 200 条。admin、support 这种固定对外地址用这个。

3. Catch-all(全部接收)

没被自定义规则命中的地址,全部转走。例如只配了 admin@,有人写成 adimn@ 或你随手发了 job-999@,都会进 Catch-all。

简历这种「一人一址 / 一职位一址」不要给每个地址建规则,开 Catch-all。200 条规则很快用完,Catch-all 一条就够。


前置:域名必须在 Cloudflare

Email Routing 是 Zone 级功能,域名 DNS 要由 Cloudflare 管。

  1. 域名 NS 切到 Cloudflare
  2. 控制台能看到这个 Zone
  3. 记下 Zone ID(Overview 右栏),API 建规则要用

注意:开启路由后,这个域名的 MX 会被改成 Cloudflare 的。原来如果 MX 指着腾讯企业邮 / Google,这个域名上的旧邮箱会收不到新信。两个邮箱系统不要抢同一个域名的 MX。

常见拆法:

  • findta.net:公司正式邮,MX 继续指企业邮
  • shuimo0413.top:对外收集简历、表单,MX 交给 Cloudflare

对外只宣传第二个域名上的地址。


控制台打开 Email Routing

路径:Email → Email Routing → 打开。

Cloudflare 会自动加上并锁住 MX、SPF,一般不用手改。大概是:

1
2
3
4
MX   @    route1.mx.cloudflare.net
MX   @    route2.mx.cloudflare.net
MX   @    route3.mx.cloudflare.net
TXT  @    v=spf1 include:_spf.mx.cloudflare.net ~all

MX 的 priority 数字看起来很随机(有的是 15、87、93),可以不管,别和别的邮件商 MX 混在一起。

开启后状态应是 ready。如果是 misconfigured,多半是 MX / SPF 被改过或没锁上。

用 dig 确认 MX 已经切过去:

1
dig MX shuimo0413.top +short

应看到 route*.mx.cloudflare.net。传播要一点时间,新域名通常很快。


添加 Destination 并验证

还是 Email Routing 页面,先加 Destination address,填你真正用来收信的邮箱。

Cloudflare 会发验证信。没点链接之前,规则即使建了也不会转发。验证信进垃圾箱很常见,先翻一下。

验证通过后,这个地址才能出现在规则的「转发到」列表里。


规则怎么匹配

一条规则 = 匹配器 matcher + 动作 action。

常用匹配器:

type 含义
literal + field: to 收件人必须完全等于某个地址
all Catch-all,剩下的全中

常用动作:

type 含义
forward 转到一个或多个已验证 Destination
drop 丢掉
worker 交给 Email Worker(可打到自己的 HTTP 接口)

控制台里 Custom addresses 对应 literal,Catch-all 对应 all。

建议:

  • 固定对外地址(admin / support / noreply)→ 自定义规则
  • 动态地址(job-123、user-8f3a)→ Catch-all
  • 确定是垃圾的前缀 → 单独 drop,避免进收件箱

用 API 批量建自定义地址

固定前缀不想在控制台一个个点,可以直接调 API。需要:

  • Cloudflare 登录邮箱
  • Global API Key(或有 Email Routing 权限的 Token)
  • Zone ID
  • 已验证的 Destination
  • 域名

凭证放 .env,不要写进仓库:

1
2
3
4
5
6
CF_EMAIL=你的Cloudflare登录邮箱
CF_API_KEY=你的Global_API_Key
CF_ZONE_ID=ZoneID
CF_DESTINATION=真实收件箱@xxx.com
CF_DOMAIN=shuimo0413.top
CF_PREFIXES=admin,sales,support,contact,noreply

Global API Key 在:My Profile → API Tokens → Global API Key。权限很大,泄露等于别人能改你整个账号。

建规则的接口:

1
POST https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/email/routing/rules

请求头:

1
2
3
X-Auth-Email: CF_EMAIL
X-Auth-Key: CF_API_KEY
Content-Type: application/json

body 示例,把 admin@shuimo0413.top 转到 Destination:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "matchers": [
    {
      "type": "literal",
      "field": "to",
      "value": "admin@shuimo0413.top"
    }
  ],
  "actions": [
    {
      "type": "forward",
      "value": ["真实收件箱@xxx.com"]
    }
  ],
  "enabled": true
}

Python 里对前缀列表循环即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json
import os
import requests
from dotenv import load_dotenv

load_dotenv()

headers = {
    "X-Auth-Email": os.getenv("CF_EMAIL"),
    "X-Auth-Key": os.getenv("CF_API_KEY"),
    "Content-Type": "application/json",
}
url = f"https://api.cloudflare.com/client/v4/zones/{os.getenv('CF_ZONE_ID')}/email/routing/rules"
domain = os.getenv("CF_DOMAIN")
destination = os.getenv("CF_DESTINATION")

for prefix in os.getenv("CF_PREFIXES", "").split(","):
    prefix = prefix.strip()
    if not prefix:
        continue
    custom_email = f"{prefix}@{domain}"
    payload = {
        "matchers": [{"type": "literal", "field": "to", "value": custom_email}],
        "actions": [{"type": "forward", "value": [destination]}],
        "enabled": True,
    }
    r = requests.post(url, headers=headers, data=json.dumps(payload))
    print(custom_email, "成功" if r.status_code == 200 else r.text)

返回 200 且 success: true 才算建成。重复创建同一地址会报错,属于正常。

Catch-all 不要走这个接口硬堆 200 条,控制台打开即可,或用:

1
PUT /zones/{ZONE_ID}/email/routing/rules/catch_all

Cloudflare 没有收件箱,读信要走 IMAP

规则只负责转发。要「爬邮件 / 收简历」,去登录 Destination 那个真实邮箱。

腾讯企业邮一般是:

1
2
3
4
5
IMAP 主机: imap.exmail.qq.com
端口: 993
加密: SSL
账号: 完整邮箱
密码: 邮箱密码或授权码

Gmail 要开 IMAP,用应用专用密码,不要用登录密码。

企业邮后台通常还要:

  • 打开 IMAP / 第三方客户端
  • 服务器公网 IP 在 API / 登录白名单里(有的环境会拦)

连上之后就是标准 IMAP:登录 → SELECT INBOX → SEARCH → FETCH RFC822。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import imaplib

mail = imaplib.IMAP4_SSL("imap.exmail.qq.com", 993)
mail.login(IMAP_USER, IMAP_PASS)
mail.select("INBOX")
status, data = mail.search(None, "ALL")
for num in data[0].split():
    status, msg_data = mail.fetch(num, "(RFC822)")
    raw = msg_data[0][1]
    # 再交给 email 模块解析

生产环境不要每次 ALL。用 UID 记下上次拉到哪,下次只取新信,附件也不会重复下。


邮件怎么解析成业务数据

原始信是 MIME。至少拆出:

  • 主题 Subject(经常是 RFC 2047 编码,要 decode_header)
  • 发件人 From
  • 时间 Date
  • 正文:优先 text/plain,没有再把 HTML 剥标签
  • 附件:Content-Disposition: attachment,简历常见 pdf / doc / docx

简历场景还要看 最初寄给谁。转发之后,Destination 里看到的 To 有时已经变成真实邮箱,真正的对外地址可能在:

  • To
  • Delivered-To
  • X-Forwarded-To
  • X-Original-To
  • 正文或主题里自己埋的 job-id

建议对外地址做成可解析的:job-{职位ID}@域名、u-{用户ID}@域名。解析到 ID 就能回表关联,不必为每个地址建一条 Cloudflare 规则。

附件保存时用 UID 做前缀,避免重名覆盖:

1
data/attachments/123_张三-简历.pdf

推荐的业务组合

对外收集、数量会涨的地址:

  1. 域名 MX 交给 Cloudflare
  2. 开 Catch-all,转到一个企业邮 / Gmail
  3. 业务系统生成 job-1024@域名 给对方
  4. 定时 IMAP 拉新信
  5. 从地址里拆出 1024,附件进对应职位

固定对外地址(官网联系、noreply):

  1. API 或控制台建 literal 规则
  2. 可以转到另一个 Destination,和简历收件箱分开

这样自定义规则只占几个名额,动态地址全靠 Catch-all。


进阶:Email Worker 代替轮询

IMAP 是你去邮箱里捞。量上来之后可以换成 Email Worker:规则动作选 worker,Cloudflare 收到信就执行一段 JS,把原始邮件 POST 到你的后端。变成推送,不再扫收件箱。

免费 Worker 有调用次数和 CPU 限制,小流量够用。简历解析、落库还是放在自己服务器上做,Worker 只负责把信送进来。


容易踩的坑

Destination 没验证
规则是绿的,信却没到。先确认验证邮件点过了。

MX 没切干净
域名上还留着企业邮 / Google 的 MX,信会跑去旧系统,路由根本看不到。dig MX 只应看到 Cloudflare。

同一个域名既要企业邮又要路由
做不到干净共存。拆域名,或接受这个域上的入站全部走 Cloudflare。

sudo 没加环境变量那种问题的邮件版:转发丢了原收件人
关联客户时不要只看 Destination 的 To,把原始地址写进本地部分(job-id@),或把相关头都存下来对照。

附件超过 25 MiB
路由会拒信。大作品集让对方网盘,不要走邮件。

自定义规则超过 200
一人一条规则会撞墙。动态地址用 Catch-all。

Global API Key 写进前端或提交到 git
能改 DNS、能改路由。只用 .env,服务器上也别打印出来。

IMAP 用登录密码被关
企业邮 / Gmail 经常要求授权码。连不上先看是不是密码策略,不是代码问题。

测试时从自己的 Destination 给自己域名发
有的邮箱会当成回环或进垃圾箱。用另一个邮箱往 admin@你的域名 发一封,看 Destination 里是否出现。


自测清单

  1. dig MX 你的域名 已是 route*.mx.cloudflare.net
  2. Destination 状态是 Verified
  3. 自定义规则或 Catch-all 已启用
  4. 用外部邮箱给 某个前缀@域名 发一封带附件的测试信
  5. 一两分钟内 Destination 收件箱能看到
  6. IMAP 能登录,脚本能把主题、正文、附件落到本地
  7. 动态地址(从没建过规则的 job-1@域名)也能进 Catch-all

这套方案的核心就一句话:Cloudflare 负责「这个域名上所有地址都能收」,真实邮箱负责「人读和脚本读」,业务 ID 写在邮箱前缀里。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
使用 Hugo 构建
主题 Stack 由 Jimmy 设计