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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 预设了三个变量,方便后续使用
$(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 则不需要该项配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 正常启动后可以使用:

1
2
3
4
5
6
7
$ 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 后面加上新的域名
1
2
3
$(primary_domain) = example.com
$(secondary_domain) = example.org
$(local_domains) = $(primary_domain) $(secondary_domain)
  1. 在 tls file 后面添加对应的证书,如果用 nginx 反代的话则添加对应的 host 路由和域名。
1
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

小结

Oh My Yandex

可以写一些稍微正经的内容,发送到 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
主题 StackJimmy 设计