想给域名配一个 you@yourdomain.com 的邮箱,很多人第一反应是买企业邮箱或托管到第三方。但如果你手里有一台服务器,又想要完全自托管、数据在自己手里的邮箱,自己跑一套邮件系统其实没有想象中难。最近把一套轻量开源邮件系统整理出来了——OpenMail,本文把它的能力、架构和怎么跑起来讲清楚。

项目地址:cnb.cool/hanyuxin/mail

这套系统解决什么

邮件服务器之所以劝退,是因为要同时维护好几个服务:收信的 SMTP、发信的提交服务、客户端收信的 POP3/IMAP、以及一个网页端。OpenMail 的思路是用 Node.js 单进程把它们全部跑起来:

  • 📥 SMTP 收信:标准入站服务器(25 端口,MX 角色),接收本地域邮件,拒绝开放中继
  • 📤 SMTP 发信:提交服务(587 端口,PLAIN/LOGIN 认证),本地收件人直投、外部收件人进并发队列
  • 📬 POP3:RFC 1939 完整实现,Foxmail / Thunderbird 直接收信
  • 🌐 WebMail:v1.3.0 全新界面,三栏布局、深浅双主题、快捷键、富文本写信、附件收发
  • 🐳 部署:Docker 一键部署,数据落盘,重启不丢邮件

换句话说,一个容器 = 一台能用的邮件服务器,开箱即用。

一条命令跑起来(最快的方式)

镜像在没检测到 DB_HOST 时自动使用内置 SQLite,数据落在 /app/data 卷,拉起来就能用:

bash
docker run -d --name openmail \
  -p 15682:15682 -p 25:25 -p 587:587 -p 110:110 \
  -v openmail-data:/app/data \
  docker.cnb.cool/hanyuxin/mail:latest

# 创建管理员账号(会自动创建域名)
docker exec -it openmail node bin/mailadmin.js adduser admin@localhost -p 123456 --admin

然后打开 http://localhost:15682 走安装向导。WebMail、安装向导和 HTTP API 都由应用原生监听,不需要反向代理。

Windows 本机试用建议换端口(25 常被占用):SMTP_PORT=2525 SUBMISSION_PORT=2587 POP3_PORT=5110 IMAP_PORT=5143 npm start。

一键部署(MySQL 8,高并发推荐)

如果是要认真跑、还打算批量发信,用 MySQL 版。镜像由 CNB 云原生构建在每次推送 main 时自动构建发布到 CNB 制品库,MySQL 由 compose 自动编排:

bash
mkdir openmail && cd openmail
curl -O https://cnb.cool/hanyuxin/mail/-/raw/main/docker-compose.prod.yml
curl -O https://cnb.cool/hanyuxin/mail/-/raw/main/deploy.sh
chmod +x deploy.sh

./deploy.sh          # 交互式:填域名/端口 → 写配置 → 拉镜像 → 启动 → 创建管理员
# 或 ./deploy.sh -y   # 全默认值

也可以手动两步:

bash
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml exec openmail \
  node bin/mailadmin.js adduser admin@example.com -p 123456 --admin

部署实操:从制品库拉镜像装起来

镜像由 CNB 云原生构建在每次推送 main 时自动构建、发布到 CNB 制品库,Docker 制品库地址:

text
docker.cnb.cool/hanyuxin/mail
  • 标签:latest(跟随最新构建)、以及 :短提交号(如 :f5f516d、:dc6c741、:aeb8bcd、:d654aee),想锁定某次构建就用提交号 tag,升级更可控
  • 架构:linux/amd64,镜像约 69.86 MiB,非常轻
  • 生产请优先用 :latest 或最新提交号,回滚时切回上一个提交号 tag 即可

下面是一台全新 Ubuntu 服务器上的完整实操(以域名 mail.example.com、管理员 admin@example.com 为例):

第 1 步:准备目录与部署文件

bash
mkdir -p /opt/openmail && cd /opt/openmail
curl -O https://cnb.cool/hanyuxin/mail/-/raw/main/docker-compose.prod.yml
curl -O https://cnb.cool/hanyuxin/mail/-/raw/main/deploy.sh
chmod +x deploy.sh

第 2 步:交互式部署(自动写配置 + 拉镜像 + 启动 + 建管理员)

bash
./deploy.sh

脚本会依次问你:域名、各服务端口、MySQL 密码,然后自动完成——写 .env → 拉 docker.cnb.cool/hanyuxin/mail:latest → docker compose up -d → 创建管理员账号。想全部用默认值直接:

bash
./deploy.sh -y      # 等价 ASSUME_YES=1 ./deploy.sh

第 3 步:确认服务与端口

bash
docker compose ps                    # 应看到 openmail 与 mysql 两个容器 running
docker compose logs --tail 50 openmail   # 检查启动日志,确认无报错
# 本机探活(WebMail/安装向导)
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:15682/

第 4 步:登录 WebMail 并配置域名

打开 http://<服务器IP>:15682,用创建的管理员登录。到「管理后台 → 域名管理」添加 example.com,系统会自动生成 DKIM 密钥。

第 5 步:一键写入 DNS 解析

到「管理后台 → DNS 配置 → 云解析自动配置」,填入你的云解析服务商(阿里云 / 腾讯云 DNSPod / 华为云)访问密钥,一键写入 A / MX / SPF / DKIM / DMARC 五条记录,然后点「检测解析」确认全部生效。

第 6 步:验证收发

bash
# 用任意 SMTP 客户端给 admin 发一封(无认证入站,仅限本地域)
node -e "
const nodemailer = require('nodemailer');
nodemailer.createTransport({ host: '127.0.0.1', port: 25 }).sendMail({
  from: 'friend@outside.com', to: 'admin@example.com',
  subject: '你好', text: '来自外部的问候'
}).then(() => console.log('已投递'));
"

发外部邮箱直接在 WebMail 里写,会进发信队列自动投递;生产环境建议配 RELAY_HOST 走邮箱服务商中继(25 端口常被云厂商封禁)。

第 7 步(可选):配 HTTPS 与 TLS

公网使用建议配正规证书。生成自签名证书先顶着:

bash
docker compose exec openmail node bin/mailadmin.js gencert /app/data/certs

要接 Let's Encrypt 证书,把证书路径填进 TLS_KEY / TLS_CERT 环境变量并重启,即可自动启用 STARTTLS。

让域名真正能收发(DNS 助手)

这是自建邮箱最容易踩的坑:邮件能发出去不等于能进收件箱。要让域名对外正常收发,需要配好 MX / SPF / DKIM / DMARC 四条记录。OpenMail 把这步自动化了:

bash
# 添加域名时自动生成 DKIM 密钥(RSA-2048)
npm run admin -- adddomain example.com

# 输出该域名应配置的全部解析记录
npm run admin -- dns example.com

# 在线检测解析是否已生效(逐条实测 MX/TXT 记录)
npm run admin -- dns example.com --check

# 轮换 DKIM 密钥(旧记录建议保留两周过渡)
npm run admin -- dns example.com --rotate

系统会生成 5 条记录:A(邮件主机)、MX(10 mail 主机)、SPF(v=spf1 a:mail主机 mx ~all)、DKIM(openmail._domainkey)、DMARC(v=DMARC1; p=none; rua=...)。

更省事的是在 Web 管理后台 → DNS 配置 里,把 阿里云 / 腾讯云 DNSPod / 华为云 的访问密钥填进去,一键把 A / MX / SPF / DKIM / DMARC 写入云解析(自动创建托管区、幂等更新、长 TXT 自动分段)。所有出站邮件都会自动附加 DKIM-Signature(rsa-sha256),收方服务器能据此验证发件域真实性。

10 万级批量收发(压测数据)

这套系统针对「群发」做了专门优化——10 万人群发,报文只存 1 份(blob 去重),用连接池 + 多行批量 INSERT + FOR UPDATE SKIP LOCKED 事务认领保证多进程安全。内置压测脚本:

bash
npm run bench -- --count 100000 --mode send    # 外发队列吞吐
npm run bench -- --count 100000 --mode ingest  # 入站收信吞吐
npm run bench -- --count 100000 --mode both

实测(开发笔记本,MySQL 8 本机):

场景 结果
外发批量入队(blob 去重,10 万收件人共享 1 份报文) ~35,000 封/秒,10 万封 2.8 秒入队
外发并发投递(32 并发 → 本机 sink) ~1,440 封/秒,10 万封 69 秒,100% 成功零丢失
入站 SMTP 收信入库(16 连接 PIPELINING) ~2,160 封/秒,10 万封 46 秒,100% 入库

外发到真实互联网时吞吐取决于对端服务器与网络。建议配 RELAY_HOST 走专业中继、按 CPU/带宽调大 OUTBOUND_CONCURRENCY、多进程水平扩展(SKIP LOCKED 保证不重复投递)。

HTTP API:给程序用的邮箱

除了网页和邮件客户端,还提供 Token 鉴权的编程接口 /api/v1,方便外部系统集成:

bash
# 创建密钥(绑定 alice 的发件身份,scope: full / send / read)
npm run admin -- apikey add alice@example.com --scope full

# 编程发信
curl -X POST http://127.0.0.1:15682/api/v1/send \
  -H "Authorization: Bearer omk_xxx" -H "Content-Type: application/json" \
  -d '{"to":"user@example.com","subject":"hi","text":"hello","attachments":[{"filename":"a.txt","contentType":"text/plain","content":"aGVsbG8="}]}'

# 收件列表 / 详情 / 统计
curl -H "Authorization: Bearer omk_xxx" http://127.0.0.1:15682/api/v1/messages?folder=inbox
curl -H "Authorization: Bearer omk_xxx" http://127.0.0.1:15682/api/v1/stats

Token 只在创建时明文返回一次(服务端只存 SHA-256),可随时吊销、立即生效。各端点 scope:/api/v1/send(send)、/api/v1/messages(read)、/api/v1/stats(read)、/api/v1/domains(read)。完整文档在 docs/API.md。

架构:v2 monorepo 分层

text
                        ┌──────────────────────────────────────┐
 外部邮件服务器 ──25──▶  │  protocols/smtp-inbound 收信(防开放中继)│──┐
                        │  quickParse 快速解析 + 规则引擎          │  │ 分组
                        └──────────────────────────────────────┘  │ 批量
                                                                  ▼
 浏览器 ────15682────▶  │  Express 原生托管 React + HTTP API     │  MySQL 8
 邮件客户端 ──110────▶  │  protocols/pop3                      │  utf8mb4
 邮件客户端 ──143────▶  │  protocols/imap(IMAP4rev1 子集)     │  连接池
 用户 ──587(认证)────▶  │  protocols/smtp-submission ─ 本地直投  │──┘
                        │                    └─ 发信队列 ─┐     │
                        └────────────────────────────────┼─────┘
                                                         ▼
                        │  queue/outbound 并发 worker 池         │
                        │  SKIP LOCKED 认领 · blob 去重 · 退避    │
                        └──────────────┬───────────────────────┘
                                   25/MX 或 RELAY_HOST ─▶ 互联网

后端分四层:protocols/(SMTP 收发/POP3/IMAP)→ services/(mail/dns/rules 业务)→ db/(驱动 + 统一异步 API)→ api/(Express 路由,只编排不写 SQL)。报文以原始 RFC822 存入 MySQL(LONGBLOB),入库只做快速头部解析并缓存纯文本供全文搜索;群发场景报文在 blobs 表存一份、queue 行引用,投递完自动清理孤儿 blob。

生产部署须知(重要)

自建邮件系统,能不能用是一回事,能不能不被拒收、不被打进垃圾箱是另一回事。这几点务必注意:

  1. DNS:为服务器 IP 配 MX / A 记录,并设置 SPF、DKIM、DMARC、反向解析(PTR)——否则发往外网大概率进垃圾箱或被拒收
  2. 25 端口:阿里云/腾讯云等默认封禁 25 端口出站,推荐配 RELAY_HOST 通过邮箱服务商 SMTP 中继发信
  3. TLS:公网使用请配正规证书(Let's Encrypt 等),或先用 npm run admin -- gencert 生成自签名证书
  4. MySQL 调优:10 万级批量建议 max_connections >= 300、innodb_buffer_pool_size >= 512M、max_allowed_packet >= 64M(compose 已按此配置)
  5. 反垃圾:公网 MX 建议前置反垃圾网关
  6. 群发合规:批量发信请遵守反垃圾邮件法规与收件方配额,控制并发避免被对端限流

CLI 管理工具速查

bash
npm run admin -- adduser bob@example.com -p 123456        # 创建用户
npm run admin -- passwd bob@example.com newpass           # 改密码
npm run admin -- listusers                                # 用户列表
npm run admin -- adddomain example.com                    # 添加域名
npm run admin -- addalias team@example.com bob@example.com  # 别名
npm run admin -- send alice@example.com bob@example.com "主题" body.txt  # 命令行发信
npm run admin -- bulksend news@example.com rcpt@test.local --count 100000  # 10 万级批量入队
npm run admin -- gencert data/certs                       # 生成自签名 TLS 证书
npm run admin -- stats                                    # 系统统计

小结

自建邮件服务器在过去是「运维劝退项目」,要同时折腾 MTA、MDA、IMAP、Web 界面。OpenMail 把这套东西收敛成一个 Node.js 进程 + 一个 Docker 容器,MySQL 高并发和 SQLite 零配置两头兼顾,DNS 记录还能自动生成和检测。如果你正想给域名配一个自托管邮箱,或者想搞一个内部邮件系统、批量通知通道,它是个值得试试的起点。

仓库地址:cnb.cool/hanyuxin/mail(README 含完整功能列表;接口细节见 docs/API.md、docs/CLIENTS.md、docs/CONFIG.md)