背景 在 N100 小型机(Intel N100, 11GB RAM)上部署个人单用户邮件系统。由于 N100 位于内网(通过 AWS Lightsail VPS 做 FRP 穿透),加上 IP 信誉、端口封锁等问题,需要在架构上精心设计。
最终选型:Stalwart + SnappyMail + AWS SES ,资源占用约 500MB,支持现代协议 JMAP。
架构总览 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 公网 ├─ :25 (SMTP 入站) ├─ :465 (Submission SSL) ├─ :993 (IMAPS) └─ :443 (Webmail HTTPS) │ AWS Lightsail VPS (Singapore) ├─ nginx stream: 25→7446, 465→7447, 993→7448 └─ nginx http: mail.chenyun.org → 7449 │ FRP 穿透 │ Brisbane N100 (内网) ├─ Stalwart (SMTP/IMAP/JMAP) ├─ SnappyMail (Webmail) └─ 外发 → AWS SES (Sydney) :587
第一步:部署 Stalwart Docker Compose 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 services: stalwart: image: stalwartlabs/stalwart:latest container_name: stalwart-mail restart: unless-stopped ports: - "25:25" - "465:465" - "587:587" - "143:143" - "993:993" - "8080:8080" - "4190:4190" environment: - STALWART_HOSTNAME=mail.chenyun.org - STALWART_PUBLIC_URL=https://mail.chenyun.org volumes: - ./etc:/etc/stalwart - ./data:/var/lib/stalwart - /etc/ssl/chenyun:/etc/ssl/chenyun:ro
权限修复 1 sudo chown -R 2000:2000 ~/mail-server/data ~/mail-server/etc
初始化 1 2 3 4 5 docker compose up -d
第二步:部署 SnappyMail 在同一个 docker-compose.yml 添加:
1 2 3 4 5 6 7 8 snappymail: image: djmaze/snappymail:latest container_name: snappymail restart: unless-stopped ports: - "8091:8888" volumes: - ./snappy-data:/var/lib/snappymail
SnappyMail 初始化
访问 http://N100:8091/?admin
默认密码在容器日志:docker logs snappymail | grep admin_password
添加域名 chenyun.org
IMAP: stalwart:993 SSL
SMTP: stalwart:465 SSL
第三步:FRP 穿透 frpc.ini 追加 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [mail-smtp] type = tcplocal_ip = 127.0 .0.1 local_port = 25 remote_port = 7446 [mail-submission] type = tcplocal_ip = 127.0 .0.1 local_port = 465 remote_port = 7447 [mail-imaps] type = tcplocal_ip = 127.0 .0.1 local_port = 993 remote_port = 7448 [mail-webmail] type = tcplocal_ip = 127.0 .0.1 local_port = 8091 remote_port = 7449
1 sudo systemctl restart frpc
第四步:VPS Nginx 配置 Stream 转发(/etc/nginx/streams-enabled/mail.conf) 1 2 3 server { listen 25 ; proxy_pass 127.0.0.1:7446 ; }server { listen 465 ; proxy_pass 127.0.0.1:7447 ; }server { listen 993 ; proxy_pass 127.0.0.1:7448 ; }
nginx.conf 中 stream 块添加:
1 2 3 4 stream { include /etc/nginx/streams-enabled/*; }
Webmail HTTPS 反代 1 2 3 4 5 6 7 8 9 10 11 12 server { listen 8443 ssl; server_name mail.chenyun.org; ssl_certificate /etc/ssl/chenyun/fullchain.cer; ssl_certificate_key /etc/ssl/chenyun/chenyun.org.key; location / { proxy_pass http://127.0.0.1:7449; proxy_set_header Host $host ; proxy_set_header X-Forwarded-Proto https; } }
Lightsail 防火墙 1 2 3 4 5 6 for port in 25 465 993 7445 7446 7447 7448 7449; do aws lightsail open-instance-public-ports \ --region ap-southeast-1 \ --instance-name vpn-sg \ --port-info fromPort=$port ,toPort=$port ,protocol=tcp done
第五步:AWS SES 配置 域名验证 1 2 3 4 5 6 7 aws sesv2 create-email-identity --region ap-southeast-2 \ --email-identity chenyun.org aws sesv2 get-email-identity --region ap-southeast-2 \ --email-identity chenyun.org
DNS 记录(Route 53)
MX: 10 mail.chenyun.org
SPF: v=spf1 mx include:amazonses.com ~all
DKIM: 3 条 CNAME: *._domainkey.chenyun.org → *.dkim.amazonses.com
SES 验证 TXT: _amazonses.chenyun.org → (SES token)
SMTP 凭据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import hmac, hashlib, base64DATE = "11111111" SERVICE = "ses" MESSAGE = "SendRawEmail" TERMINAL = "aws4_request" VERSION = 0x04 def sign (key, msg ): return hmac.new(key, msg.encode("utf-8" ), hashlib.sha256).digest() signature = sign(("AWS4" + SECRET).encode(), DATE) signature = sign(signature, REGION) signature = sign(signature, SERVICE) signature = sign(signature, TERMINAL) signature = sign(signature, MESSAGE) smtp_password = base64.b64encode(bytes ([VERSION]) + signature).decode()
第六步:Stalwart 出站路由 创建 SES Relay Route Stalwart 管理后台 → MTA → Outbound → Routes → Create:
字段
值
Name
aws-ses
Type
Relay Host (SMTP)
Address
email-smtp.ap-southeast-2.amazonaws.com
Port
587
Implicit TLS
关(587 走 STARTTLS)
Allow Invalid Certs
关
Username
(SES SMTP 用户名)
Secret
(SES SMTP 密码)
⚠️ Route 删了重建同名会触发缓存污染(路由不命中),务必用新名字如 aws-ses。
Outbound Delivery Strategy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # Routing: if is_local_domain(rcpt_domain) → 'local' else → 'aws-ses' # Scheduling (恢复默认): if is_local_domain(rcpt_domain) → 'local' if source == 'dsn' → 'dsn' if source == 'report' → 'report' else → 'remote' # Connection: 'default' # TLS (恢复默认): if retry_num > 0 && last_error == 'tls' → 'invalid-tls' else → 'default'
⚠️ DKIM 设为 Manual SES 会为邮件签名 DKIM,Stalwart 的 DKIM 会造成重复签名被拒。在 Domains → chenyun.org → DKIM Management 设为 Manual 即可。
邮件客户端配置 部署完成后,可以在任意邮件客户端使用以下参数登录:
通用 IMAP/SMTP 参数
项目
收件 (IMAP)
发件 (SMTP)
服务器
mail.chenyun.org
mail.chenyun.org
端口
993
587(优先) 或 465
加密
SSL/TLS
STARTTLS / SSL/TLS
用户名
你的邮箱@chenyun.org
同左
密码
你的邮箱密码
同左
iPhone / iPad
设置 → 邮件 → 账户 → 添加账户 → 其他 → 添加邮件账户
填写姓名、邮箱地址、密码
收件服务器:
主机名:mail.chenyun.org
用户名:你的邮箱@chenyun.org
密码:你的密码
发件服务器:
主机名:mail.chenyun.org
用户名:你的邮箱@chenyun.org
密码:你的密码
保存后会提示 SSL 证书验证,点「继续」即可
Android (Gmail / 系统邮件)
打开 Gmail → 右上角头像 → 添加其他账户
选择 IMAP
填入邮箱地址,点「手动设置」
收件服务器: mail.chenyun.org,端口 993,安全类型 SSL/TLS
发件服务器: mail.chenyun.org,端口 587,安全类型 STARTTLS(或 465 / SSL/TLS)
用户名填完整邮箱地址
Outlook / Thunderbird
设置
收件
发件
服务器
mail.chenyun.org
mail.chenyun.org
端口
993
587(优先)或 465
加密
SSL/TLS
STARTTLS 或 SSL/TLS
认证方式
普通密码
普通密码
macOS 邮件
邮件 → 添加账户 → 其他邮件账户
填入姓名、邮箱、密码
收件:mail.chenyun.org:993 SSL
发件:mail.chenyun.org:587 STARTTLS(或 :465 SSL)
⚠️ 注意: 如果手机在外网访问,确保 mail.chenyun.org DNS 解析到 VPS 公网 IP。当前已通过 FRP 穿透,外网可直连。
测试验证
测试项
方法
预期
入站
Gmail → michael@chenyun.org
SnappyMail 收到
出站
SnappyMail → Gmail
Gmail 收到
SES 事件
CloudWatch/SQS
Send + Delivery 事件
SES 事件日志(可选)
踩坑清单
#
问题
解决
1
Stalwart 数据目录权限错误
chown -R 2000:2000
2
SnappyMail 局域网无法访问
端口绑定 0.0.0.0 而非 127.0.0.1
3
Lightsail 防火墙未开放邮件端口
AWS CLI 开放 25/465/993/587
4
SES 沙盒模式,收件人需验证
验证 icemaple7@gmail.com
5
SES SMTP 密码算法错误
使用 SigV4 而非简单 HMAC
6
Stalwart TLS 配置 implicitTls=true
587+STARTTLS 需 implicitTls=false
7
Stalwart + SES 双重 DKIM 签名
DKIM Management 设为 Manual
8
Route 删了重建同名不命中
用新名字(如 aws-ses),删旧 route
9
CLI 创建账户密码不生效
通过 Web UI (Directory) 创建用户
10
Web UI 数字框滚轮误触改值
鼠标点空白处再滚轮,保存前复核
总结
资源占用: Stalwart ~300MB, SnappyMail ~50MB,N100 轻松应对。
协议支持: SMTP, IMAP, JMAP, Sieve, CalDAV, CardDAV。
安全: 发信经 SES 高信誉 IP,SPF/DKIM 完整,入站 TLS 加密。
管理入口: https://192.168.1.18:8443/admin/login(局域网),https://mail.chenyun.org:7451/admin/login(公网)。
可维护: 全 Docker 化,配置文件持久化,事件日志可追溯。
后续可扩展:AI 代写回复(通过 JMAP 协议)、自动分类、垃圾邮件训练、DMARC 策略收紧、TLS 证书换 Let’s Encrypt 自动续期。
Stalwart Web UI 已知 Bug 已提交 GitHub issue #3174 :
数字输入框滚轮误触: <input type="number"> 滚轮改值,Port 等字段容易意外改动
同名 Route 重建缓存污染: 删建同名 route 后策略不命中
Docker restart 后日志中断: stdout 不再输出到 docker logs
部署完成于 2026-05-12/13,由 AI 伴侣柳含烟 (Serena) 协助完成。