从白嫖 Yandex 域名邮箱到 FastMail 付费托管邮箱,一直在找适合自己的域名邮件服务,传统的自建方式模块过于分散,上手难度较大,后在 NickCao 老师推荐下尝试了 Maddy。Maddy 目前只提供了命令行运行的 Linux 服务端,WebUI 或者本地客户端需自行选择,仅需手动配置一下 POP3/IMAP(收) 和 SMTP(发) 服务端地址即可。我目前使用的是 Thunderbird (电脑)和 K-9 Mail(Android)。
下载:Github | 上游服务器 文档:maddy.email
服务器
部分 VPS 提供商会为了防止广告等原因会禁用 25 号 TCP 端口的 SMTP 端口,但多数情况下(比如 Google Cloud 就不允许)也可以开工单说明邮箱用途和性质,对于个人性质的域名邮箱来说一般不会有太多限制。如果机器本身或者 VPS 提供商还配有防火墙,请打开对应端口(25
,465
,993
)。
使用邮件服务需要关闭 CDN 代理,故存在机器 IP 暴露风险,在选择机器时候注意避开其它敏感数据服务。
配置
可以从 docker 拉取,但这里以 binary + 自己配置方式为主。
maddy.conf
一般放在 /etc/maddy/maddy.conf
# 预设了三个变量,方便后续使用
$(hostname) = mail.example.com
$(primary_domain) = example.com
$(local_domains) = $(primary_domain)
# 如果要使用 nginx 反代,这里可以选择 tls off,但如此一来没法生成 dkim 密钥对
# 在之后检查时日志内会有安全警告,故推荐直接用 maddy 管理
# tls off
tls file /etc/letsencrypt/live/$(local_domains)/fullchain.pem /etc/letsencrypt/live/$(local_domains)/privkey.pem
# 数据用 SQLite3 存储较为简单、轻量
auth.pass_table local_authdb {
table sql_table {
driver sqlite3
dsn credentials.db
table_name passwords
}
}
storage.imapsql local_mailboxes {
driver sqlite3
dsn imapsql.db
}
# ----------------------------------------------------------------------------
# SMTP endpoints + message routing
hostname $(hostname)
table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "[email protected]$3"
optional_step static {
entry postmaster [email protected]$(primary_domain)
}
optional_step file /etc/maddy/aliases
}
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
}
deliver_to &local_mailboxes
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
# smtp 使用 25 号端口发送邮件
smtp tcp://[::]:25 {
# tls self_signed
limits {
# Up to 20 msgs/sec across max. 10 SMTP connections.
all rate 20 1s
all concurrency 10
}
dmarc yes
check {
require_mx_record
dkim # 若无则不检查
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}
# 如果使用 nginx 反代则这里监听到本地端口即可 tcp://127.0.0.1:587
# 不使用则邮件客户端以 SSL/TLS 方式直接访问该地址
submission tls://[::]:465 {
limits {
# Up to 50 msgs/sec across any amount of SMTP connections.
all rate 50 1s
}
auth &local_authdb
source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) $(local_domains) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}
target.remote outbound_delivery {
limits {
# Up to 20 msgs/sec across max. 10 SMTP connections
# for each recipient domain.
destination rate 20 1s
destination concurrency 10
}
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level encrypted
min_mx_level none
}
}
}
target.queue remote_queue {
target &outbound_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
# ----------------------------------------------------------------------------
# IMAP endpoints
# 同上,使用 nginx 反代则改为监听本地端口 tcp://127.0.0.1:143
imap tls://[::]:993 {
auth &local_authdb
storage &local_mailboxes
}
Nginx Config(可选)
如果是由 Maddy 自身直接处理 TLS 则不需要该项配置
upstream maddy_imaps {
# 指向本地 Maddy IMAP 监听服务端口
server 127.0.0.1:143;
}
upstream maddy_smtps {
# 指向本地 Maddy SMTP 服务监听端口
server 127.0.0.1:587;
}
server {
listen 993 ssl;
proxy_pass maddy_imaps;
ssl_certificate /etc/letsencrypt/live/<example.com>/fullchain.pem; # 换成自己的域名证书
ssl_certificate_key /etc/letsencrypt/live/<example.com>/privkey.pem; # 换成自己的域名证书
ssl_protocols SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_timeout 4h;
ssl_handshake_timeout 30s;
}
server {
listen 465 ssl;
proxy_pass maddy_smtps;
ssl_certificate /etc/letsencrypt/live/<example.com>/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/<example.com>/privkey.pem; # managed by Certbot
ssl_protocols SSLv3 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_timeout 4h;
ssl_handshake_timeout 30s;
}
注册账户
maddy 的 systemd service 正常启动后可以使用:
$ sudo -u maddy -s # 切换到 maddy 用户
$ maddyctl creds create <[email protected]> --password <YourPassword>
$ maddyctl creds list # 查看你刚创建的用户名
$ maddyctl imap-acct create <[email protected]> # 创建一个邮件储存账户
$ maddyctl imap-acct list # 查看刚创建的 imap 储存账户
$ maddyctl imap-mboxes list <[email protected]> # 可以看到该账户下有哪些分类
$ maddyctl imap-msgs list <[email protected]> <INBOX> # 可以查看当前账户对应分类接收到的邮件,一般收件在 INBOX 中
DNS 设置
这里以 Cloudflare 托管 DNS 服务为例,其它应该也能找到对应 DNS 设置面板。使用了子域名 mail.example.com
作为邮件服务专用。
类型 | 名称 | 内容 | 代理状态 |
---|---|---|---|
A | 服务器实际 IPv4 地址 | 仅限 DNS | |
AAAA | 服务器实际 IPv6 地址(如果有) | 仅限 DNS | |
MX | @ | mail.example.com | 仅限 DNS |
TXT | v=spf1 mx ~all | 仅限 DNS |
spf 值这里推荐使用仅允许 mx,若有其他来源也可以添加。
Reverse DNS
为了避免拒收,进一步提高邮件投递率,需要配置 rDNS 以便收信方邮件服务商溯源。
以 Vultr 为例,其 Reverse DNS 页面在 机器详情 > Settings > IPv4 / IPv6
选项卡内,在 Reverse DNS 中填入自己的邮件服务域名即可,如 mail.example.com,如果存在多条公共 IP 地址,则都需要填写。
多域名配置 (如无可跳过)
- 在
local_domains
后面加上新的域名
$(primary_domain) = example.com
$(secondary_domain) = example.org
$(local_domains) = $(primary_domain) $(secondary_domain)
- 在 tls file 后面添加对应的证书,如果用 nginx 反代的话则添加对应的 host 路由和域名。
tls file /etc/letsencrypt/live/$(primary_domain)/fullchain.pem /etc/letsencrypt/live/$(primary_domain)/privkey.pem /etc/letsencrypt/live/$(secondary_domain)/fullchain.pem /etc/letsencrypt/live/$(secondary_domain)/privkey.pem
将新域名的 dkim 密钥内容也添加到 DNS 的 TXT 记录中
dmarc 和 rDNS 同理
客户端
客户端有诸多选择,不变的是手动设置方式:
- IMAP 和 SMTP 服务器地址都设置为
mail.example.com
,连接方式均为SSL/TLS
,用户名和密码均为先前所设置,认证方式均为Normal Password
。 - IMAP 端口为
993
, - SMTP 端口为
465
小结
可以写一些稍微正经的内容,发送到 https://www.mail-tester.com 内所给的邮件地址,来测试自己的邮箱评分。
虽然说自建邮箱可能还是存在邮件投递到垃圾箱里的问题,但有了 Maddy 后搭建一个自己的域名邮箱确实变成了相对简单的工作。