Skip to content

在子域名上使用 Cloudflare Email Routing

Posted on:April 15, 2023 at 03:26 PM
0

Cloudflare Email Routing 是一个免费的 catch-all 邮件路由方案,它可以帮助您将电子邮件转发到正确的收件人,并提高电子邮件的可靠性和安全性。使用 catch-all 邮件服务,您可以为每个网站和服务生成一个随机的、一次性的电子邮件地址。这些地址将被路由到您的 catch-all 邮箱,您可以随时查看它们,从而保护您的真实电子邮件地址不被泄露。而 Bitwarden 则可以自动生成和存储这些随机电子邮件地址,使您不必手动创建和管理它们。

-- ChatGPT

Cloudflare Email Routing 只支持收邮件,不支持发邮件,所以不能把它作为我们的主邮件服务。但是 Cloudflare 又限制它只能用在顶级域名上,如果能想办法绕过限制,用一个子域名专门做 catch-all 邮件服务就好了。经过一些尝试,我找到了这样做的办法。

在启用 Cloudflare Email Routing 功能时,Cloudflare 自动为我们添加了 4 条 DNS 记录,包括 3 条 MX 记录和一条 TXT 记录。发邮件时,发信服务器会向 DNS 服务器请求这几条记录,这样才能获得收件人所在的服务器 IP。

我们只需要手动把这 4 条记录移动到子域名上,就可以在这个子域名上使用 Cloudflare Email Routing 了。

同时,如果还想让其它子域名使用 Cloudflare Email Routing,只需将新增的子域名 CNAME 指向到之前设置好的那个子域名上就可以了。

如果你只是想简单的将发往此域名的所有邮件都转给同一个收件邮箱,那么只需要在 Cloudflare 提供的界面上设置好Catch-all 地址就够了。

Cloudflare 的自定义地址功能界面也只是为顶级域名设计的,我们用了子域名就不能直接通过 Web 界面来控制不同地址的转发策略了。好在Catch-all 地址可以设置成转发到 Cloudflare Worker,这样我们就可以自己写程序来灵活控制转发策略了。

我随手写了个简单的 Worker 来自定义转发邮件的规则,利用 Worker KV 来存储规则,这样仍然可以实现在 Web 界面上直接改规则。

const handleResult = async (message, policy) => {
    console.log("policy " + policy);
    if (policy == "drop") {
        return;
    }
    if (policy == "reject") {
        message.setReject("blocked");
        return;
    }
    if (policy.indexOf("@")) {
        await message.forward(policy);
        return;
    }
};

export default {
    async email(message, env, ctx) {
        console.log("to " + message.to);
        const to = message.to.split("@")[0];
        console.log("name " + to);

        let policy = "drop";
        
        let pre = "full:";
        const full = await env.MAIL.list({ prefix: pre });
        for (var k of full.keys) {
            if (to == k.name.substring(pre.length)) {
                policy = await env.MAIL.get(k.name);
                await handleResult(message, policy);
                return;
            }
        }

        pre = "regex:";
        const regex = await env.MAIL.list({ prefix: pre});
        for (var k of regex.keys) {
            const r = new RegExp(k.name.substring(pre.length));
            if (r.test(to)) {
                policy = await env.MAIL.get(k.name);
                await handleResult(message, policy);
                return;
            }
         }

         await handleResult(message, policy);
    }
}

这个 Worker 支持对收件地址 @ 前的内容做完全匹配和 Regex 匹配来决定这封邮件的去向。需要新建一个 Work KV Namespace,并以 MAIL 为 key 绑定到此 Worker 上。

KV 中存储的内容可以是下面这样的。

KeyValue说明
full:abc123@qq.com完全匹配名字为 abc 的收件地址,并转发到 123@qq.com
regex:.+drop通过 regex .+ 匹配收信地址 @ 前的部分,符合条件的邮件直接丢弃掉,不再转发
full:123reject匹配名字为 123 的收件地址,邮件不转发,并且告知发信方此邮件被拒绝了