本帖最后由 seek 于 2024-11-27 14:36 编辑
https://github.com/foxcpp/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 SMTPTransfer->Exchange ClearText
- 465 SMTPs User->Submission TLS
- 143 IMAPDelivery->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 hello@example.com
maddy imap-acct create hello@example.com
获取 DKIM 的公钥
/ # cat data/dkim_keys/*.dns
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG...zAuhVJKPYUPTnJ3yTIzi4R2XbzfwIDAQAB
复制保存,将在设置 DNS 记录使用
设置 DNS 记录
- A 记录,将邮箱域名(
mail.example.com )指向你的服务器 IP 地址
- MX(Mail Exchange)记录指向你的邮件服务器地址,确保可以接收邮件。将邮件域(
hello@example.com ,其中 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 |
mail 邮件 |
A |
71.2.3.1 |
A |
2 |
mail 邮件 |
AAAA |
2607:b102::e 2607:b102::e |
|
3 |
smtp SMTP (SMTP) |
CNAME |
mail.example.com |
CNAME |
4 |
imap IMAP 公司 |
CNAME |
mail.example.com |
|
5 |
@ |
MX |
mail.example.com |
MX |
6 |
@ |
TXT |
v=spf1 mx ~all v=spf1 mx ~全部 |
SPF |
7 |
mai 邮件 |
TXT |
v=spf1 mx ~all v=spf1 mx ~全部 |
|
8 |
_dmarc |
TXT |
v=DMARC1; p=quarantine; ruf=mailto:hello@example.com |
DMARC |
9 |
default._domainkey |
TXT |
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG...zAuhVJKPYUPTnJ3yTIzi4R2XbzfwIDAQAB |
DKIM |
最终,我在 cloudflare 新添加的 DNS 记录如下
测试检查
测试自建邮件服务器 mail.example.com ,邮箱 hello@example.com 收发邮件情况。
我们使用 Mailspring —— 一个支持 Mac、Linux 和 Windows 的、开源的,邮件客户端来操作。
使用 hello@example.com 连接后
- 往自己的 Gmail 邮箱发邮件,检查能否收到邮件。
- Gmail 收到邮件后,在 Gmail 邮箱后回复,检查在邮件客户端 Mailspring 能否收到邮件。
如上图,可以正常收发邮件。
最后,mail-tester 测试下得分 9.5,这是未针对内容调整,调整后估计分值可以加高。
在线工具
|