返回
Featured image of post Maddy 自建邮件服务

Maddy 自建邮件服务

从白嫖 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 作为邮件服务专用。

类型名称内容代理状态
Amail服务器实际 IPv4 地址仅限 DNS
AAAAmail服务器实际 IPv6 地址(如果有)仅限 DNS
MX@mail.example.com仅限 DNS
TXTmailv=spf1 mx ~all仅限 DNS

spf 值这里推荐使用仅允许 mx,若有其他来源也可以添加。

Reverse DNS

为了避免拒收,进一步提高邮件投递率,需要配置 rDNS 以便收信方邮件服务商溯源。

以 Vultr 为例,其 Reverse DNS 页面在 机器详情 > Settings > IPv4 / IPv6 选项卡内,在 Reverse DNS 中填入自己的邮件服务域名即可,如 mail.example.com,如果存在多条公共 IP 地址,则都需要填写。

多域名配置 (如无可跳过)

  1. local_domains 后面加上新的域名
$(primary_domain) = example.com
$(secondary_domain) = example.org
$(local_domains) = $(primary_domain) $(secondary_domain)
  1. 在 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
  1. 将新域名的 dkim 密钥内容也添加到 DNS 的 TXT 记录中

  2. dmarc 和 rDNS 同理

客户端

客户端有诸多选择,不变的是手动设置方式:

  • IMAP 和 SMTP 服务器地址都设置为 mail.example.com,连接方式均为 SSL/TLS,用户名和密码均为先前所设置,认证方式均为 Normal Password
  • IMAP 端口为 993
  • SMTP 端口为 465

小结

可以写一些稍微正经的内容,发送到 https://www.mail-tester.com 内所给的邮件地址,来测试自己的邮箱评分。

虽然说自建邮箱可能还是存在邮件投递到垃圾箱里的问题,但有了 Maddy 后搭建一个自己的域名邮箱确实变成了相对简单的工作。

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
Ariel AxionL's Blog
Built with Hugo
Theme Stack designed by Jimmy