给 Stalwart 邮件服务器挂上阿里云邮件推送 SMTP 中继

1051 字
5 分钟
给 Stalwart 邮件服务器挂上阿里云邮件推送 SMTP 中继

问题背景#

服务器 192.168.x.x 上跑了 Stalwart Mail Server v0.16.19,发信域 vtdd.vip。发件出口是电信家宽 IP 14.19.x.x,没有 PTR 反向解析。

发往 Gmail 时被退信:

550-5.7.25 The IP address sending this message does not have a PTR record setup
550-5.7.25 or the corresponding forward DNS entry does not point to the sending IP.

PTR 只能由 IP 所有者设置,家宽 IP 无法自己做,只能改用 SMTP 中继(smarthost)发信。

方案选择#

  • 阿里云邮件推送 smtpdm.aliyun.com:465:个人实名可用,域名验证、发信地址、SMTP 密码、SPF/DKIM/DMARC 一条龙;本次实测成功。
  • 腾讯云 SES 理论上可行,但 2026-03-02 起新开通的个人实名账号不再支持 SMTP 发信,只能 API/控制台,不适合直接给 Stalwart 当 smarthost。

前置准备:阿里云控制台#

  1. 开通 邮件推送https://dm.console.aliyun.com)。
  2. 发信域名 → 新建 vtdd.vip,按提示在 DNS 加:
    • 域名所有权 TXT
    • SPF TXT:v=spf1 a mx include:spf1.dm.aliyun.com ip4:14.19.x.x ~all
    • DKIM TXT:aliyun-cn-hangzhou._domainkey
    • DMARC TXT:_dmarc.vtdd.vipv=DMARC1; p=none; rua=mailto:dmarc_report@service.aliyun.com
  3. 回阿里云点验证,等 DNS 生效。
  4. 发信地址 → 新建 zero@vtdd.vip,发信类型务必选 「触发邮件」
  5. 给该地址设置 SMTP 密码(不是阿里云登录密码,是单独的 SMTP 密码)。

注意:一个域名只能有一条 SPF TXT,已有记录要合并而不是新增第二条。

Stalwart 侧配置#

1. 建 Relay 路由#

路径:Settings → MTA → Outbound → Routes → Create

字段
Namealiyun
Route typeRelay Host
Addresssmtpdm.aliyun.com
Port465
ProtocolSMTP
Implicit TLS
Allow Invalid Certs
Auth Usernamezero@vtdd.vip
Auth Secret → Secret value阿里云 SMTP 密码

2. 让非本地邮件走这个路由#

路径:Settings → MTA → Outbound → Strategy

route 表达式的 ELSE'mx' 改为 'aliyun'

if is_local_domain(rcpt_domain) → 'local'
else → 'aliyun' ← 改这一处

必须带单引号

3. 关闭 DANE / MTA-STS#

路径:Settings → MTA → Outbound → TLS Strategies

字段
DANEdisable
MTA-STSdisable

走中继后,收件方策略不再适用于中继连接,留着会失败。

4. 保存并重启#

右上角 Save & Reload 后,必须重启容器

Terminal window
docker restart stalwart

坑点:Stalwart 的 Strategy(单例策略)改完后光 Save / Reload 不会热加载,不重启就仍然走直连 mx。这是本次最大的坑。

验证中继是否真的生效#

Stalwart 容器默认不往 stdout 写日志docker logs 是空的),/api/* 管理接口也查不了队列,只能抓网络层。

方法:监听 /proc/net/tcp 的 465 出站连接#

上传到服务器后台跑:

Terminal window
nohup bash -c 'for i in $(seq 1 300); do
docker exec stalwart cat /proc/net/tcp | \
awk "NR>1{n=split(\$3,a,\":\"); if(n==2 && a[2]==\"01D1\") print systime()\" \"a[1]\" \"\$4}"
sleep 1
done' >/dev/null 2>&1 &

然后立刻发一封测试邮件。查看结果:

Terminal window
cat /tmp/relay_watch.log
  • rem_address106.11.x.x(对应 smtpdm.aliyun.com)→ 走中继成功
  • rem_address14.19.x.x 或没有记录 → 仍直连,检查 Strategy ELSE 是否保存并重启。

注意:一定要用 $3(远端地址)匹配 01D1,不能全行 grep ':01D1',否则会误命中本机 465 的 LISTEN 条目。

认证结果与垃圾箱问题#

Gmail 收到的原始邮件头:

Authentication-Results: mx.google.com;
spf=pass (google.com: domain of zero@vtdd.vip designates 47.90.196.146 as permitted sender)
dkim=pass header.i=@vtdd.vip header.s=aliyun-cn-hangzhou header.b=...
dkim=neutral (invalid public key) header.i=@vtdd.vip header.s=aliyun-cn-hangzhou-cn ...
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=vtdd.vip
  • SPF pass:阿里云 IP 被授权。
  • DKIM passaliyun-cn-hangzhou 这条签名通过了。
  • dkim=neutral (invalid public key) 对应另一个 selector aliyun-cn-hangzhou-cn——该 selector 没有 DNS 记录,是中性结果,不影响 DMARC
  • DMARC pass:因为 p=none,SPF 对齐通过即可。

如果邮件仍进垃圾箱,通常是阿里云共享 IP 信誉低、域名太新、没有历史发信记录导致的 Gmail 内部评分问题。处理办法:

  1. 多发几封并正常打开/回复,建立信誉。
  2. 在 Gmail 里把这封信标为「非垃圾」,把 zero@vtdd.vip 加入联系人。
  3. 持续进垃圾箱再考虑换其他中继服务。

关键坑点汇总#

  1. Strategy ELSE 带单引号'aliyun' 不能写成 aliyun
  2. 改完 Strategy 必须重启容器,Save & Reload 不够。
  3. 路由名找不到时 Stalwart 静默回退直连,不会报错——表现就是「阿里云没记录、Gmail 能收到但进垃圾箱」。
  4. 一个域名只能有一条 SPF TXT,合并而不是叠加。
  5. DMARC 先保持 p=none,观察稳定后再收紧,否则 DKIM 不对齐时会被直接拒收。

参考命令#

容器内 465 连通性测试:

Terminal window
docker exec stalwart bash -c 'printf "EHLO mail.vtdd.vip\r\nQUIT\r\n" | timeout 15 openssl s_client -connect smtpdm.aliyun.com:465 -servername smtpdm.aliyun.com -crlf -quiet'

重启并确认健康:

Terminal window
sudo docker restart stalwart
sleep 25
docker ps --filter name=stalwart

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

给 Stalwart 邮件服务器挂上阿里云邮件推送 SMTP 中继
https://vtdd.vip/posts/给-stalwart-邮件服务器挂上阿里云邮件推送-smtp-中继/
作者
Zero
发布于
2026-09-06
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Zero
有三件事人类都要经历:出生、生活和死亡。他们出生时无知无觉,死到临头,痛不欲生,活着的时候却又怠慢了人生。——拉布吕耶尔
分类
标签
最新动态
站点统计
文章
29
动态
8
分类
9
标签
38
总字数
19,814
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.14.3
文章许可
CC BY-NC-SA 4.0