用 BillionMail 自建邮局:两种装法、七个容器,以及上线前必须改掉的默认值
BillionMail 是自托管的邮件服务器 + 邮件营销 / NewsLetter 平台,用 Go 写,AGPLv3。它把 Postfix、Dovecot、Rspamd、RoundCube 打包在一起,外面套一层发信域名管理、模板和统计界面,可以理解为「自己的 Mailchimp」,但底层就是一台标准 SMTP/IMAP 服务器。
- 仓库:
- 官网:
- 在线 demo:(账号
billionmail/ 密码billionmail)
自建邮局的关键步骤不在于安装,而在于「25 端口能不能出站」「IP 有没有 PTR」「域名记录对不对」。如果这些外部条件不满足,装完之后依然发不出去。因此首要任务是确认外部条件。
装之前先确认的三个外部条件
1. 25 端口出站。 多数云厂商默认封禁 25 端口出站,需要提交工单解封;如果不解封,就改用 587 submission 加中继。检查方式:
nc -vz gmail-smtp-in.l.google.com 25
或者 telnet 到收件方 MX 记录的 25 端口。请求超时就是被封。
2. 发信 IP 的 PTR / rDNS。 反向解析必须指向 mail.example.com 这类主机名,与 Postfix 的 myhostname 一致。这个一般也在云厂商控制台提交工单修改,自己改不了。
3. 域名解析。 除了 A 记录和 MX 记录,还需要 SPF、DKIM、DMARC 三条记录。装好之后用 bm show-record 可以把需要的记录直接打印出来,照抄到 DNS 服务商。
另外,新 IP 没有发信信誉,刚装好就群发基本会进垃圾箱,先小流量养一段时间。
两种安装路径与差异
一键脚本
要求 root,只支持 x86_64 / aarch64,不支持 32 位。
cd /opt && git clone https://github.com/aaPanel/BillionMail && cd BillionMail && bash install.sh
脚本做的事:
- 检查 25/465/587/110/143/993/995 端口占用;
- 用
/dev/urandom生成随机的ADMIN_USERNAME、ADMIN_PASSWORD(8 位)、SafePath(8 位)、REDISPASS(32 位);DBPASS生成后写入DBPASS_file.pl并chmod 600; - 自动安装 Docker CE;
- 执行
setenforce 0,并把/etc/selinux/config改成SELINUX=disabled; - 在 LXC 容器里会提示可能装不好(对嵌套/非特权环境有要求);
- 支持
--domain、--TZ参数。域名只传一级域名时,会自动补成mail.example.com作为 Postfix 的myhostname。
Docker Compose
cd /opt && git clone https://github.com/aaPanel/BillionMail && cd BillionMail && cp env_init .env && docker compose up -d
两条路线的核心差异在口令:一键脚本会随机生成密码,并在安装完成后告诉你登录信息;Compose 路线直接把 env_init 复制成 .env,里面全是写死的默认值,不会自动改。
env_init 的默认值:
ADMIN_USERNAME=billion
ADMIN_PASSWORD=billion
SafePath=billion
DBPASS=NauF7ysRYyt9HTOiOn4JjIAL3QcRZnzj
REDISPASS=zKLnZQr3riFpcS2lEy3MOtfncztaCGKp
BILLIONMAIL_HOSTNAME=mail.example.com
SMTP_PORT=25
SMTPS_PORT=465
SUBMISSION_PORT=587
IMAP_PORT=143
IMAPS_PORT=993
POP_PORT=110
POPS_PORT=995
REDIS_PORT=127.0.0.1:26379
SQL_PORT=127.0.0.1:25432
HTTP_PORT=80
HTTPS_PORT=443
TZ=Etc/UTC
IPV4_NETWORK=172.66.1
FAIL2BAN_INIT=y
IP_WHITELIST_ENABLE=false
RETENTION_DAYS=7
用 Compose 装,up -d 之前先把 ADMIN_PASSWORD、SafePath、DBPASS、REDISPASS、BILLIONMAIL_HOSTNAME 改掉。装完再改就要动数据库和配置了。
容器拓扑
所有服务在 bridge 网络 billionmail-network 上,子网 172.66.1.0/24。
| 服务 | 镜像 | 端口 | 挂载 / 备注 |
|---|---|---|---|
| pgsql-billionmail | postgres:17.4-alpine | 默认 127.0.0.1:25432 → 5432 | 数据 ./postgresql-data,socket ./postgresql-socket |
| redis-billionmail | redis:7.4.2-alpine | 127.0.0.1:26379 → 6379 | entrypoint 走 ./conf/redis/redis-conf.sh,sysctl net.core.somaxconn=4096 |
| rspamd-billionmail | billionmail/rspamd:1.2 | — | 依赖 redis,日志 ./logs/rspamd,RETENTION_DAYS 默认 7 |
| dovecot-billionmail | billionmail/dovecot:1.6 | 143 / 993 / 110 / 995 | 挂 vmail-data、ssl,ulimit nofile 20000/40000 |
| postfix-billionmail | billionmail/postfix:1.6 | 25 / 465 / 587 | 挂 postfix-data、rspamd-data、ssl |
| webmail-billionmail | roundcube/roundcubemail:1.6.11-fpm-alpine | — | ROUNDCUBEMAIL_REQUEST_PATH=/roundcube |
| core-billionmail | billionmail/core:4.9.3 | 80 / 443 | 挂 /var/run/docker.sock:ro、.env、conf、logs、vmail-data,cap_add NET_BIND_SERVICE / NET_ADMIN / NET_RAW,FAIL2BAN_INIT=y |
日常管理命令:
bm help # 命令列表
bm default # 查看登录信息
bm show-record # 打印域名的 DNS 记录
bm update # 升级
WebMail 在 /roundcube/ 路径下。
上线后的检查与坑
默认口令。 Compose 路线下 ADMIN_USERNAME=billion / ADMIN_PASSWORD=billion / SafePath=billion,等于管理后台门户大开。SafePath 是后台的隐藏入口路径,默认值意味着别人都知道走哪进去。装完立刻改,用 bm default 确认当前生效的登录信息。
DB 密码写法。 一键脚本把 DBPASS 写进 DBPASS_file.pl 并设成 600 权限,密码不在 .env 明文里;Compose 路线密码就在 .env 里,DBPASS=NauF7ysRYyt9HTOiOn4JjIAL3QcRZnzj 这个值是公开的,不改等于数据库密码全世界都知道——虽然它只监听 127.0.0.1。Redis 的 REDISPASS 同理。
fail2ban。 FAIL2BAN_INIT=y 表示容器启动时会初始化 fail2ban,配合 IP_WHITELIST_ENABLE=false(默认不启用白名单)。自己调试期间如果被反复封 IP,先看这里。
SELinux 被关。 一键脚本会 setenforce 0 并把配置改成 SELINUX=disabled,这是永久性的。宿主机上还跑着别的服务的话,需要自己权衡,脚本不提供保留 SELinux 的选项。
docker.sock。 core-billionmail 挂了 /var/run/docker.sock:ro,虽然是只读,但能读到宿主机 Docker 的控制面信息,能列举容器、镜像、网络。80/443 也是由这个 core 容器直接终结。对外暴露之前想清楚这一点。
数据库与 Redis。 只绑 127.0.0.1,是这套默认配置里比较合理的一处,别为了远程连库改成 0.0.0.0。
许可证。 AGPLv3,改了代码并对外提供服务,需要开源衍生的部分。
什么时候别用
需要企业级投递能力(专用 IP 池、投递信誉托管、退信处理流水线)的场景不适合自己部署这套方案——BillionMail 解决的是「有一个自己的发信平台」,不解决「从零建立发信信誉」。
云厂商不解封 25 端口出站、又不打算配 587 中继的,装完发不出信,只是得到一个能收信的 IMAP 服务器。
宿主机上跑着不能关 SELinux 的业务、或者对 Docker socket 暴露敏感的环境,一键脚本这条路直接排除;Compose 路线也要自己评估 core 容器挂 socket 的代价。
导出订阅者名单、跑大批量营销邮件、又不想承担 IP 被拉黑后的运维成本,这类需求交给第三方 ESP 更省事。