上一篇《企业邮箱发 139 邮箱总进垃圾箱:一次 SPF / DKIM / DMARC 排查实录》解决了 DNS 认证缺项,邮件恢复正常进收件箱,我以为这事就完结了。结果当天晚上,同一套企业邮箱、同一个收件人、三项认证齐全,测试邮件又进了 139 的垃圾箱——区别只有一个:这封信不是我自己点的发送,而是让 AI 助手(企业微信机器人)代发的。这篇记录怎么用三封对照邮件把问题钉死,以及顺手解掉的另一个谜:为什么自己的邮件会被 139 标注”来自境外”。
先说结论
- 机器人代发指纹是进垃圾箱的决定性因素。企业微信接口代发的邮件,源码里带有
EX-QQ-AI-BOTID头,且From显示名是机器人名——收件方反垃圾引擎据此加权,直接进垃圾箱。 - 发件地址用主地址还是别名,对落点没有影响。两条地址走 SMTP 直发都进了收件箱。
- SMTP 直发的邮件会被腾讯调度到海外网关投出,因此被 139 标注”来自境外”——但实测垃圾分为 0、正常进收件箱,标签没有实际负面影响。
现象:同一邮箱,三种发法,三种待遇
域名是同一个 *.site 子域,SPF / DKIM / DMARC 三项认证前一天刚全部补齐并验证生效,收件人都是我自己的 139 邮箱:
| # | 发件地址 | 发信通道 | 发件人显示名 | 139 落点 |
|---|---|---|---|---|
| 1 | 别名地址 | 企业微信机器人接口代发 | 机器人名(YSBot) | ❌ 垃圾箱 |
| 2 | 主地址 | SMTP 直连 | 本人名字 | ✅ 收件箱 |
| 3 | 别名地址 | SMTP 直连 | 本人名字 | ✅ 收件箱 |
三封信主题相近、正文长短相近、发送时间在一小时内。变量只剩两个:发信通道和发件地址。对照实验正好能把它们分开。
定位:邮件源码里写着答案
在 139 邮箱网页版打开进垃圾箱的那封 →「更多」→「查看邮件源码」。这里有个小坑:139 的 RichMail 系统通常不回注 Authentication-Results 行,上一篇提到的”看认证结果”技巧在 139 侧经常派不上用场。但没有关系,源码里的另外几个头部足够说明问题:
X-RM-SPAM-FLAG:00000005 ← 139 反垃圾引擎的垃圾判定,非 0 即被判了
EX-QQ-AI-BOTID: 33777000132280559 ← 腾讯给这封信打上的"AI 机器人发送"标记
From: "YSBot" <you@mail.example.com> ← 发件人显示名是机器人名
DKIM-Signature: v=1; a=rsa-sha256; d=mail.example.com; s=qfog2609; ...
← 签名正常,通道认证没问题
判读思路:
X-RM-SPAM-FLAG:00000005说明垃圾判定是 139 自己的反垃圾引擎打出来的;EX-QQ-AI-BOTID+ 机器人显示名,等于明明白白告诉收件方”这是机器批量发的”——个人邮箱收到一封显示名为机器人的来信,像垃圾邮件几乎是合理怀疑;- DKIM 签名的
d=、s=与后台配置完全一致,说明 DNS 认证链路完好,问题不在”谁发的”认证上,而在”怎么发的”方式上。
关键是:企业微信的邮件发送接口没有任何参数可以去掉这些标记——EX-QQ-AI-BOTID 是接口代发的固有指纹,显示名取自机器人配置。想不带指纹,只能换通道。
解决:绕开接口,SMTP 直连
腾讯企业邮箱本身提供标准 SMTP 服务,直连发送的邮件没有机器人指纹,而且发件地址、显示名完全由自己指定——顺便解决了另一个痛点:企业微信接口代发时 From 自动取邮箱的”默认发信地址”,想用主地址还是别名,得去网页版手动切换;SMTP 直发想用哪个就用哪个。
前提准备(一次性):
- 邮箱网页版「设置 → 收发信设置」开启 IMAP/SMTP 服务(服务器
smtp.exmail.qq.com,SSL 端口 465); - 「设置 → 账户 → 账户安全」生成客户端专用密码(安全密码)。它是独立于登录密码的专用凭据,泄露了可以随时单独重置;不要把登录密码写进脚本。
发送端用 Python 标准库就够了,不需要任何第三方依赖:
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
msg = MIMEText(open("body.txt", encoding="utf-8").read(), "plain", "utf-8")
msg["Subject"] = Header("测试邮件", "utf-8")
msg["From"] = formataddr((str(Header("你的名字", "utf-8")), "you@example.com"))
msg["To"] = "18xxxxxxxxx@139.com"
with smtplib.SMTP_SSL("smtp.exmail.qq.com", 465, timeout=30) as s:
s.login("you@example.com", "客户端专用密码") # 密码放本地配置文件,别提交进仓库
s.sendmail("you@example.com", ["18xxxxxxxxx@139.com"], msg.as_string())
发完做对照实验,三封信的落点就是开头那张表:SMTP 直发的两封(主地址、别名)全部进收件箱,接口代发的那封进垃圾箱。变量分离干净利落。
彩蛋:为什么 SMTP 直发的信被标”来自境外”
SMTP 直发的两封虽然进了收件箱,列表里却被 139 标注了”来自境外”,而机器人代发那封反而没有。继续看源码最后一跳:
# SMTP 直发(标"来自境外"):
Received: from smtpbguseast1.qq.com (unknown[54.204.34.129])
by rmmx-lg_mx13-12072 (RichMail) with SMTP id ...
# 机器人接口代发(不标):
Received: from bg5.exmail.qq.com (unknown[43.154.155.102])
by rmmx-lg_mx18-12111 (RichMail) with SMTP id ...
54.204.x.x 是 AWS 美东(弗吉尼亚)网段,主机名里的 useast1 就是 US-East-1——腾讯把 SMTP 提交的邮件调度到了海外边界网关投出,139 按 IP 归属地打了标签;接口代发走内部队列,从国内出口集群投出,所以没标签。这个调度由腾讯内部决定,客户端无法指定。
要不要紧?看数据:境外出口那封 X-RM-SPAM-FLAG:00000000——垃圾分是 0,比代发那封的 00000005 还干净,且正常进收件箱。所以这个标签目前纯属观感问题,接受现状即可,后续留意它是否开始影响落点就行。
排查速查表
| 现象 | 结论与动作 |
|---|---|
认证三项齐全,仍进垃圾箱,且 From 显示名是机器人名 |
查源码有无 EX-QQ-AI-BOTID 等代发标记;换 SMTP 直连发送 |
139 源码里找不到 Authentication-Results |
正常,RichMail 不回注;改看 X-RM-SPAM-FLAG 和 DKIM-Signature 的 d=/s= |
| 想按次指定发件地址(主地址/别名) | 企微接口无 from 参数;SMTP 直发可任意指定自有地址和显示名 |
| SMTP 直发的邮件被标”来自境外” | 腾讯出口网关调度所致(实测 AWS US-East-1),垃圾分不受影响,可接受 |
| 子域发信地址是不是必须改绑主域 | 子域确属减分项,但实测 SMTP 直发进收件箱;先跑起来观察,不急着动 DNS |
写在最后
上一篇把”DNS 认证”这层确定性问题解决后,我以为邮件投递的坑已经见底,结果”发信通道”这个更隐蔽的变量紧跟着浮了出来——同一个邮箱,换个发送方式,命运截然不同。AI 助手代发确实方便,但收件方的反垃圾系统认的是种种”像人”的信号:显示名、发送指纹、历史行为。让邮件看起来是”你”发的,而不只是”以你的地址”发的,这件事在接口代发场景下做不到,那就换一条做得到的路。
方法论上,这次再次验证了两把刀的好用:对照实验(控制变量,一封封发)和邮件源码(所有结论都要落到具体头部字段上)。玄学问题的答案,通常就写在 header 里。