两周前,CogniPrep 的开发者还在文章里坚持一个观点:对垃圾邮件应该打标记,而不是直接丢弃。现在,代码把这条路线推翻了。
当时那篇带观点的文章标题就写着,修复方案是打标记而非丢弃。被分类器判定为垃圾的邮件仍然会被转发,只是换了个发件地址,并在主题前加了一个前缀。代码里写得很清楚:垃圾邮件检查标记的邮件仍会转发,但来自这个地址,接收方只需一条精确的 from: 过滤规则就能归档。
这套逻辑现在全部删掉了。分类器的判定直接终止请求:被垃圾检查标记的邮件会被丢弃,而不是转发。
为什么之前的论证输了
支持打标记的理由是它可逆。什么都不销毁,收件箱拿到一条过滤规则,误判的邮件还躺在文件夹里,随时能找到。
实际发生的情况是,那个文件夹变成了第二个需要维护的系统,而且代码对它没有发言权。整个设计依赖 Gmail 里一条精确匹配 from: 地址的过滤规则,这条规则是手工在控制面板里写的,仓库里没有任何东西能让它保持同步。改个地址名,或者加第二个域名,过滤规则就会悄悄失效,而且失效方向是最糟的那种:推销邮件重新回到主收件箱,还带着一个没人处理的 [Spam] 前缀。
而“我还能看到它,只是被归档了”的诚实版本是:归档的垃圾邮件是一个你最终还是会去翻的文件夹,这恰恰是过滤规则本该省下的注意力。
于是标记变成了丢弃。这让误判的代价从“放错文件夹”升级为“彻底消失”,任何地方都没有副本。之所以还能接受,全靠它前面跑的那些检查。
顺序就是全部的安全论证
在处理程序内部,顺序如下:
- 这是我们会转发的地址吗?MX 记录指向邮件服务商,所以域名下每个地址都存在,发给其中任何一个的邮件都会到达同一个 webhook。不在监控地址列表里的,直接提前返回。
- 是 DMARC 报告吗?那些是没人读的机器报告,从不离开服务商。
- 抓取并解析原始邮件。大小检查两次,下载主机必须匹配允许列表。
- 是现有支持工单的回复吗?如果 threadTicketReply 认领了它,处理程序就返回。这一步很关键,而且被刻意放在分类器之前。
- 到这一步才做垃圾检查。判定为垃圾就记录并返回。
- 附件被解码,邮件被转发。
第 4 步排在第 5 步前面,是丢弃能站得住脚的原因。客户回复支持工单是最不能丢的东西,而他们的回复根本到不了分类器:它按所属线程匹配到工单并归档,没有任何模型意见参与。
把分类器移到第 6 步的附件循环之前也是有意为之,只是没那么戏剧性。被丢弃的邮件不再先把每个附件读进内存并做 base64 编码。旧代码做完这些工作,然后把结果扔给一封已经被标记为推销的邮件。
分类器只在肯定回答时删除
这个模块对失败方向写得很明确:失败时放行。缺少密钥、超时、数据库错误或无法解析的回复,都算作“不是垃圾”,因为漏掉一封垃圾邮件损失一秒,漏掉一个客户损失一个客户。
这句话现在承担的作用比写它的时候更大。以前,失败放行意味着放过一封没被标记的推销邮件。现在,它意味着放过一封没被删除的邮件。所有不是明确 {"spam": true} 的路径最终都会被转发:没有 API 密钥、超时、查询发件人时数据库出错、回复无法解析、答案不确定。提示词本身也这么说:如果不确定,回答不是垃圾。
还有两件事把影响范围压得很小。注册用户作为发件人被直接信任,永远不会到达模型,所以用注册地址写信的客户根本不会被分类。而且问模型的问题不是“这看起来像商业邮件吗”,而是一个方向问题,因为我们自己也在做外联,大量合法邮件本身就是商业的,来自带着电话号码的陌生人。问题是谁在向谁推销。
删除之后还能观察到什么
丢弃最不能做的一件事就是变得不可见。日志事件随行为改了名:resend:email.received:flagged-spam 变成了 resend:email.received:discarded-spam。
邮件没了,但“有一封邮件被丢弃”这个事实,以及它发往哪一类地址,都被记录了下来。这足以回答“它是不是突然开始一天丢十封”,而不必保留邮件本身,而保留邮件本身正是丢弃要避免的。
进入同一队列的另一扇门仍然是帮助页面上的网页表单,它会问问题有多紧急,完全不经过垃圾邮件分类器,关于页面也写明了你在给谁写信。如果丢弃哪天真的吃掉了一封真实邮件,那个表单是吃不掉的路径。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.