Maddy 是一款用 Go 语言开发的邮件服务器,部署方便,资源占用少,是一款适合个人使用的邮箱服务器。
在我的 CloudCone 1C1G 机 docker 搭建后查看容器,CPU 使用 0.03%,内存使用 20M,所以 1C1G 跑起来毫无压力。
准备
- 一台 VPS,支持开启 25 端口和 rDNS
- 一个域名,支持管理 A/AAAA 记录、MX 记录、TXT 记录等
- 邮箱域名的证书和密钥(使用 Certbot 申请)
安装 docker
docker-compose.yml 的配置如下
version: "3.8"
services:
maddy:
image: foxcpp/maddy:latest
container_name: maddy
ports:
- 25:25
- 143:143
- 465:465
- 993:993
volumes:
- /opt/docker/data/maddy:/data
- /etc/letsencrypt/live/mail.example.com/fullchain.pem:/data/local_certs/cert.pem:ro
- /etc/letsencrypt/live/mail.example.com/privkey.pem:/data/local_certs/key.pem:ro
environment:
- TZ=Asia/Shanghai
- MADDY_HOSTNAME=mail.example.com
- MADDY_DOMAIN=example.com
启动后开放的端口及作用
- 25 SMTP
Transfer->Exchange ClearText
- 465 SMTPs
User->Submission TLS
- 143 IMAP
Delivery->User ClearText
- 993 IMAPs
Delivery->User TLS
- 587 ESMTP
User->Submission ClearText/STARTTLS
获取 tls 证书
安装 Certbot
# Ubuntu/Debian
sudo apt-get install certbot
# Centos
sudo yum install certbot
获取 tls 证书:
sudo certbot certonly --standalone -d mail.example.com
按照提示操作,将获得证书和私钥,存储在 /etc/letsencrypt/live/mail.example.com/
目录下。
配置 maddy
# 预设了三个变量,方便后续使用
$(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
tls file /data/local_certs/cert.pem /data/local_certs/key.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 "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
optional_step file /data/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
}
启动 maddy
docker compose up -d
创建 maddy 用户
# 进入容器
docker exec -it maddy /bin/sh
# 创建邮箱
maddy creds create [email protected]
maddy imap-acct create [email protected]
获取 DKIM 的公钥
/ # cat data/dkim_keys/*.dns
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG...zAuhVJKPYUPTnJ3yTIzi4R2XbzfwIDAQAB
复制保存,将在设置 DNS 记录使用
设置 DNS 记录
- A 记录,将邮箱域名(
mail.example.com
)指向你的服务器 IP 地址 - MX(Mail Exchange)记录指向你的邮件服务器地址,确保可以接收邮件。将邮件域(
[email protected]
,其中example.com
就代表邮件域)指向第一步的邮箱域名,优先级默认为 10(数字越低,优先级越高) - PTR 记录(或者叫 rDNS),可以通过 ip 地址反向解析出邮箱域名。不设置也没关系,但是会被某些服务器标记为垃圾邮件
- SPF(Sender Policy Framework)是用来指定哪些服务器有权限代表你的域名发送邮件
- DMARC(Domain-based Message Authentication, Reporting, and Conformance) 用于指定邮件的处理策略,以及接收未通过验证的邮件报告
- DKIM(DomainKeys Identified Mail) 用于对邮件进行数字签名,验证邮件是否被篡改
DNS 验证
# install dig
apt-get install dnsutils
# 验证 MX 记录
$ dig +short MX example.com
mail.example.com
# 验证 A 记录
$ dig +short A mail.example.com
71.2.3.1
# 验证 rDNS(PTR) 记录
$ dig +short -x 71.2.3.1
mail.example.com
以 mail.example.com
为邮件服务器的域名为例
序号 | 名称 | 类型 | 内容 | 说明 |
---|---|---|---|---|
1 | A | 71.2.3.1 |
A | |
2 | AAAA | 2607:b102::e |
||
3 | smtp | CNAME | mail.example.com |
CNAME |
4 | imap | CNAME | mail.example.com |
|
5 | @ | MX | mail.example.com |
MX |
6 | @ | TXT | v=spf1 mx ~all |
SPF |
7 | TXT | v=spf1 mx ~all |
||
8 | _dmarc | TXT | v=DMARC1; p=quarantine; ruf=mailto:[email protected] |
DMARC |
9 | default._domainkey | TXT | v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG...zAuhVJKPYUPTnJ3yTIzi4R2XbzfwIDAQAB |
DKIM |
最终,我在 cloudflare 新添加的 DNS 记录如下
测试检查
测试自建邮件服务器 mail.example.com
,邮箱 [email protected]
收发邮件情况。
我们使用 Mailspring —— 一个支持 Mac、Linux 和 Windows 的、开源的,邮件客户端来操作。
使用 [email protected]
连接后
- 往自己的 Gmail 邮箱发邮件,检查能否收到邮件。
- Gmail 收到邮件后,在 Gmail 邮箱后回复,检查在邮件客户端 Mailspring 能否收到邮件。
如上图,可以正常收发邮件。
最后,mail-tester 测试下得分 9.5,这是未针对内容调整,调整后估计分值可以加高。
在线工具
- 垃圾邮件匹配度检测 https://www.mail-tester.com/?lang=zh
- MxtoolBox,查询 MX 记录,DKIM 等用于检查是否配置正确 https://mxtoolbox.com/SuperTool.aspx