给服务器上的程序部署邮件服务十分简单,装个 Postfix 就搞定了。然而给人用的话就远远不够了。之所以要干这事,主要原因是之前使用的 Yandex 邮箱老出问题,丢邮件都算小事了,它还不让我登录 Web 界面,非要我填写我从未设置的密保问题的答案……
准备工作
要部署邮件服务,首先当然要有域名和服务器了。需要注意的是,最好使用可以设置 PTR 记录的服务器,有些邮件服务器会要求这个。
邮件传输代理
这是最重要的部分。邮件传输代理,简称 MTA,是监听 TCP 25 端口、与其它邮件服务器交互的服务程序。我最常用的是 Postfix,给服务器上的程序用的话,它相当简单易用。但是要给它配置上 IMAP 和 SMTP 登录服务、以便给人类使用的话,就很麻烦。好在之前听群友说过 maddy,不仅能收发邮件,还支持简单的 IMAP 服务。唯一的缺点是不支持通过 25 端口发送邮件——需要走 465 或者 587 端口,登录之后才能发件。它的账号系统也是独立于 UNIX 账号的,给程序使用需要额外的配置。
具体配置方面,首先是域名和 TLS 证书。我不知道为什么,它在分域名证书的选择上有些问题,最后我干脆全部用通配符证书解决了事。数据库我使用的是 PostgreSQL。要使用本地 peer 鉴权的话,需要把 host
的值设置为 PostgreSQL 监听套接字所在的目录,比如我是这样写的:
dsn "user=maddy host=/run/postgresql dbname=maddy sslmode=disable"
PostgreSQL 监听套接字所在目录是编译时确定的。maddy 是 Go 写的,并不使用 libpq,因此它无法自动确定这个目录在哪里,需要手动指定。
关于邮箱别名,可以使用文本文件配置,也可以使用数据库查询指定。别名功能可以用来实现简单的邮件列表功能——发往某一个地址的邮件会被分发到多个实际收件人的邮箱中。但是它不支持去重,也就是说,往包含自己的别名地址发送邮件,自己会额外收到一份。设置起来大概是这样子的:
table.chain local_rewrites { optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3" optional_step static { entry postmaster postmaster@$(primary_domain) } optional_step file /etc/maddy/aliases step sql_query { driver postgres dsn "user=maddy host=/run/postgresql dbname=maddy sslmode=disable" lookup "SELECT mailname FROM mailusers.mailinfo WHERE $1 = ANY(alias) and new = false" } }
哦对了,那个 postmaster 地址需要手动合并,不然就要每个域名创建一个账号了。在别名文件里写上 postmaster@host2: postmaster@host1
就行了。
maddy 会经常检查别名的修改时间然后自动重新加载,数据库查询当然是查出来是什么就是什么,所以还是比 Postfix 每次跑 postalias
命令要方便不少。
DNS 配置
邮件域名的 MX 记录当然要设置上的。邮件服务器 IP 的 PTR 记录也要设置到服务器的域名上(A / AAAA 记录指到服务器)。SPF 的记录也不能忘。DMARC 和 DKIM 的记录没那么重要,不过推荐按 maddy 的文档设置上。
我还给域名设置 imap、imaps 和 submission 的 SRV 记录,但似乎客户端们并不使用它们。
这些设置好之后就可以去 https://email-security-scans.org/ 发测试邮件啦。
反垃圾
maddy 内建对 rspamd 的支持,所以就用它好了。直接在 smtp
的 check
节里写上 rspamd
就好了。rspamd 跟着官方教程走,也基本不需要什么特别的设置,就是官方给的 nginx 配置有些坑人。我是这样设置的:
location /rspamd/ { alias /usr/share/rspamd/www/; expires 30d; index index.html; try_files $uri $uri/ @proxy; } location @proxy { rewrite ^/rspamd/(.*)$ /$1 break; proxy_pass http://127.0.0.1:11334; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; }
注意这里给静态文件设置了过期时间,不然每次访问都要下载那些文件,非常慢。我是挂载在子路径下的,需要通过 rewrite
配置把子路径给删掉再传给 rspamd,不然会出问题。
邮件客户端自动配置
上边提到了 SRV 记录并不管用。实际上管用是在 https://autoconfig.example.org/mail/config-v1.1.xml
的配置文件。具体可以看 Lan Tian 的《编写配置文件,让 Thunderbird 自动配置域名邮箱》这篇文章。
Web 邮件客户端
使用的是 Roundcube,是一个 PHP 软件。可以跟着 ArchWiki 的教程配置。注意最好别跟着配置 open_basedir
,因为会影响同一 php-fpm 实例上的其它服务。另外记得配过期时间,不然每次都要下载静态资源,很慢的。
因为上边部署了 rspamd 反垃圾服务,所以也可以给 Roundcube 启用一下 markasjunk
插件,并在 /usr/share/webapps/roundcubemail/plugins/markasjunk/config.inc.php
配置一下对应的命令:
$config['markasjunk_spam_cmd'] = 'rspamc learn_spam -u %u -P PASSWORD %f'; $config['markasjunk_ham_cmd'] = 'rspamc learn_ham -u %u -P PASSWORD %f';
不过我配置这个之后,命令会按预期被调用,但是 rspamd 的统计数据里不知为何总显示「0 Learned」。把垃圾邮件通过命令行手动喂给它又会提示已经学过该邮件了。