使用 Cloudflare 搭建域名邮箱

Cloudflare 2022年推出了 电子邮件路由 功能,通过这个功能,我们可以直接在 cloudfalre 接收域名邮件,无需再申请其他第三方的域名邮箱服务。

关于该功能的开通已有多篇文章进行介绍,本文就不再赘述。

使用该服务时,可以选择开通 Call all 功能,将所有除自定义地址以外的邮箱,全部进行转发。

转发方式有两种,一种是转发至另一个邮箱,但这种方式有很大弊端。一是该方式仍需使用传统邮箱进行邮件接收,域名邮箱多用于客户服务方面,最好有一种编程的方式可以直接读取邮件。第二点,该方式依托第三方邮箱服务,服务商会对邮箱进行限制,如邮件过滤,登录验证,邮箱容量等,会导致邮件并不能 100% 到达。

第二种方式则使用 Cloudflare Worker 功能,将接收到的电子邮件通过 Worker 处理。结合自己开发的后端服务,将邮件存储到自己的服务器上,这样就可以通过编程的方式来获取邮箱。

Cloudfalre Worker:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const { PostalMime } = require('postal-mime');


async function streamToArrayBuffer(stream, streamSize) {
let result = new Uint8Array(streamSize);
let bytesRead = 0;
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
result.set(value, bytesRead);
bytesRead += value.length;
}
return result;
}

export default {
async email(message, env, ctx) {
let from = message.headers.get("from");
let to = message.headers.get("to");
let subject = message.headers.get("subject");
let data = "";
if (message.raw != null) {
const rawEmail = await streamToArrayBuffer(message.raw, message.rawSize);
const parser = new PostalMime.default();
const parsedEmail = await parser.parse(rawEmail);
data = parsedEmail.html;
}
let response = await fetch("https://example.com/email", {
method: "POST",
headers: {
"Content-type": "application/json; chatset=UTF-8"
},
body: JSON.stringify({
"from": from,
"to": to,
"subject": subject,
"data": data
})
});
console.log(response.status, response.statusText, await response.text());
}
}

以上 Worker 在部署时,需要使用 Wrangler CLI.

部署完成后,在 call all 中选择该 Worker,域名所收到的邮箱将会全部转发至 https://example.com/email

from, to 不采用 message.from, message.to 是因为在实践中发现, message.from 有事会设置为代发邮箱的邮件地址,无法看到具体发件人的邮件地址

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
func NewEmail(c *fiber.Ctx) error {
email := new(emailBody)
c.BodyParser(email)
email.CreatedAt = int(time.Now().Unix())
//处理 发件人 收件人
reg, _ := regexp.Compile(`<(.*)>`)
fromSubMatch := reg.FindStringSubmatch(email.From)
if len(fromSubMatch) != 0 {
email.From = fromSubMatch[1]
}
toSubMatch := reg.FindStringSubmatch(email.To)
if len(toSubMatch) != 0 {
email.To = toSubMatch[1]
}

domainSplit := strings.Split(email.To, "@")
if len(domainSplit) == 2 {
email.Domain = domainSplit[1]
}
rows, err := db.Query("INSERT INTO `table` (`from`,`to`,`subject`,`data`,`domain`,`created_at`) VALUES(?,?,?,?,?,?)", email.From, email.To, email.Subject, email.Data, email.Domain, email.CreatedAt)
if err != nil {
log.Println(err)
return c.SendString("ERROR")
}
defer rows.Close()
return c.SendString("OK")
}

在该代码中,会将转发过来的邮件存储到数据库中,以便日后查询。需要注意的是 from, to 参数均进行正则过滤。是因为 from, to 参数可能含有 发件人,收件人自定义名称,需要进行删除。

对于发件服务,我目前使用 AWS SES 进行发件。该服务可以直接仅配置子域的 mx 记录,完成域名的认证。然后就可以自定义发件人名称向不特定邮箱进行发件。