用个人域名发邮件的配置流程记录

很早以前就搞过用个人域名收发邮件,在稳健地跑过多年没管也逐渐没在用以后发件似乎挂了,加上今年换服务器了,之前的流程肯定需要修复/迁移。因为最近需要发邮件, 今天 前天终于用摸鱼时间把这件事情重新搞了一遍,顺便做个记录。

因为上次搞是很久以前了,对这些东西并不熟悉,用到的很工具也偏冷门,文档也很难读,所以采取了步步为营的策略,每次先实现一个能跑通的,再在此基础上修改,一步一步完善。

用到的一个重要工具是 https://www.mail-tester.com ,可以检测邮件有没有发出去和邮件质量。每天有免费使用的次数限制,不过可以改变ip来增加次数。

一些背景:

  • 域名注册商送了免费的邮件转发服务,所以收件不是问题。
  • 域名上还留有上次折腾留下的spf、DMARC、DKIM记录,也还记得这些记录的用途。
  • 上一次折腾采取的方案是从gmail的web或者各种邮件客户端(我在用开源的K-9 mail)连接到自己服务上的OpenSMTPD,OpenSMTPD转给 DKIMproxy做DKIM签名,收到做完签名后的邮件再转给gmail的smtp服务器实际发送。

方案0 直接使用现有服务

成本:

  • 需要拥有域名
  • 可能需要提供个人信息或(和)花钱

一开始也想过要不这回别折腾了,直接使用现有服务,比如G Suite(发现已经叫Google Workspace了)或者国内的各种xx企业邮。因为不准备花钱,G Suite就排除了。正好手上有一个当年搞的免费版阿里企业邮(现在好像已经没法注册免费版了,怎么都找不到入口),发现可以添加一个域名别名,就拿子域名mail.<mydomain.xxx>配好它需要的CNAME和MX记录试了试。 文档

只用子域名试水是因为对这个方案没什么信心,不想影响域名注册商送的邮件转发。而且虽然手上有一些更安全的邮箱,真要长期使用这种不安全的邮件服务也觉得怪怪的。

使用上还是很流畅的,等域名生效完成验证,再新增了一个叫me的员工账户,就得到了me@mail.<mydomain.xxx>,收发都在上面。

用上面说过的mail-tester试了一下,发现没有DKIM签名。而搜了一下想设置的话需要人工联系客服申请 ,等于没法设置。

于是这种方案到此也就没有什么存在的价值了,毕竟没法提供DKIM签名的话相比下面要说的方案1没什么优势。

方案1 gmail自己发自己

成本:

  • 需要拥有域名
  • 需要拥有一个gmail账号或同类服务

这个方案其实很简单,打开gmail的设置(See all settings),在Accounts and Import这个tab的Send mail as那里点击Add another email address。Email address填想用来发件的地址,比如 me@<mydomain.xxx>,下一步SMTP Server填gmail自己的smtp服务器地址smtp.gmail.com,账号是自己的gmail账号,密码最好在谷歌的账号设置里面创建一个应用专用密码。之后gmail会给me@mail.<mydomain.xxx>发一封确认邮件 Gmail Confirmation - Send Mail as me@<mydomain.xxx>,点进链接点确认,就算验证完成了。如果收件也是转发到gmail的话,之后用这个邮件地址收发都是在gmail里面,使用很流畅。

记得要配置一下spf,不过DKIM签名的问题仍然得不到解决,有较大风险进垃圾邮件箱。 另外,在有些收件邮箱那里,会直接把“由xxx@gmail.com代发”显示在发件人后面。

方案2 gmail到自己的smtp服务器

成本:

  • 需要拥有域名
  • 需要拥有一个可以使用相应端口的服务器
  • 需要拥有一个gmail账号或同类服务(或smtp服务,或直接使用该服务器发件)

这个方案本身相比方案1并没有什么优势,主要是为了方案3做铺垫。可以用来实现smtp服务器的工具有很多,我选择了OpenSMTPD,一是因为之前用过,二是因为它相对来说是功能最简单的,配置起来更方便。

稍微编辑一下默认的配置(/etc/smtpd.conf)就可以工作了:

table aliases file:/etc/smtpd/aliases
table secrets file:/etc/smtpd/secrets
table creds   file:/etc/smtpd/creds

# To accept external mail, replace with: listen on all

pki <mydomain.xxx> cert "<fullchain.pem>"
pki <mydomain.xxx> key "<privkey.pem>" 

listen on eth0 port 587 tls pki <mydomain.xxx> auth <creds>

action "local" maildir alias <aliases>
action "relay" relay host smtp+tls://gm@smtp.gmail.com:587 auth <secrets>

# Uncomment the following to accept external mail for domain "example.org"
#
# match from any for domain "example.org" action "local"
match for local action "local"
match auth from any for any action "relay"

其中/etc/smtpd/secrets里面是“gm <gmail用户名> <应用专用密码>“,/etc/smtpd/creds里面是”<用户名> <密码的哈希>“,这里哈希可以使用smtpctl encrypt生成。上次的配置是服务器上直接创建了一个用户用于smtp连接,这种玩法实在不怎么合适,所以这次还是用了 Credentials tables了。

证书(也就是pki部分)我直接使用了服务器上https用的证书,因为一开始按文档自己用openssl签的证书一直有问题,连接的时候报错域名不匹配。

配置好以后编辑之前我们在gmail里面的设置,SMTP Server改为<mydomain.xxx>,用户名和密码自己定一个并且写入上面说的creds表。

可以参考的文档:

配置的时候发现opensmtp的配置格式相比之前也有了变化,从以前那套accept变成了 action match。

一些坑:

  1. 低版本OpenSMTPD对openssl 3.0的支持有问题导致gmail上配置的时候报错

相关issue

可以看到其实已经修复了,但是我服务器用的是Ubuntu,OpenSMTPD的版本十分低。好在找了一下,Debian unstable有新版的包,下载deb,幸运地顺利安装上了,问题解决。

  1. 550 Invalid recipient

gmail配置好了,但是发件并不成功,很快收到gmail的回信Message not delivered 原因是 The response from the remote server was: 550 Invalid recipient: 。

最后发现是配置的时候需要显式地写明for any,默认的match from any并不能包含任意收件人。

处理完这些,mail-tester终于收到邮件了。当然,得分依然不高。

如果不希望被看出是gmail代发的,方案2也可以做一些变体。

一个办法是去掉host smtp+tls://gm@smtp.gmail.com:587 auth <secrets>,从服务器上直接发出去。这样在mail-tester上的得分甚至能更高,因为gmail的smtp ip反而进了一些黑名单。然而考虑到真实场景,这样一封来自一个名不见经传的新ip的邮件,其实更可能被直接拒收。

另外一个办法是改为转发给一些专门发件服务,例如mailgun、sendinblue。不过它们毕竟是用于群发订阅邮件的,在管理已发邮件上可能没有使用gmail那么方便。

方案3 gmail到自己的smtp服务器并进行DKIM签名

成本:

  • 需要拥有域名
  • 需要拥有一个可以使用相应端口的服务器
  • 需要拥有一个gmail账号或同类服务(或smtp服务,或直接使用该服务器发件)

DKIM签名这步,以前用的是DKIMproxy,既然opensmtpd的文档推荐opensmtpd-filter-dkimsign,那这次就试试它了。两者的工作方式也是不一样的,前者可以理解为一个一直运行着的网络服务,监听某个端口,把收到的邮件加上签名再从另一个端口发回去;后者是一个在需要的时候单次运行的程序,作为filter加进opensmtpd的配置。

于是安装opensmtpd-filter-dkimsign(在ubuntu上用apt install得到的即可),编辑smtpd.conf配置文件如下

table aliases file:/etc/smtpd/aliases
table secrets file:/etc/smtpd/secrets
table creds	file:/etc/smtpd/creds

# To accept external mail, replace with: listen on all

pki <mydomain.xxx> cert "<fullchain.pem>"
pki <mydomain.xxx> key "<privkey.pem>" 

filter "dkimsign" proc-exec "filter-dkimsign -d <mydomain.xxx> -s smtp -k /path/to/dkim/private/key" \
		   user root group root
listen on eth0 port 587 tls pki <mydomain.xxx> auth <creds> filter "dkimsign"

action "local" maildir alias <aliases>
action "relay" relay host smtp+tls://gm@smtp.gmail.com:587 auth <secrets>

# Uncomment the following to accept external mail for domain "example.org"
#
# match from any for domain "example.org" action "local"
match for local action "local"
match auth from any for any action "relay"

同样的,转发给gmail的smtp服务器这步可以换成直接发件或者其他smtp服务。

到此大功告成,在mail-tester上取得了9分,扣的一分是因为gmail的smtp服务本身进了两个黑名单。

send mail 9

完工