背景
迁移到新 SMTPS 服务后,发现生产环境持续产生退信:
<某功能账号@example.com>: 550 5.7.54 Unable to relay recipient in non-accepted domain
排查发现:一个 CI 自动化(功能/脚本)账号在用户表里的邮箱是占位地址(@example.com),它持续自动发起大量测试,每个测试完成的通知都尝试发给这个占位邮箱 → 新邮件服务器严格拒绝往外部/无效域投递 → 每次都退信。
旧邮件服务器时期,往这类无效地址发信未暴露成明显问题(旧服务器投递策略较宽松,且当时退信通道未打通、无人关注);新服务器严格拒绝后,退信就显现出来了。
根因
- 系统给功能/脚本账号默认生成的邮箱就是
<账号名>@example.com(占位,不存在) - 这类账号作为”发起测试的用户”时,完成通知会发给它的占位邮箱 → 退信
方案
与其逐个修改用户数据,不如从发信逻辑层面过滤掉占位邮箱——治本,一次性覆盖所有功能/脚本账号。
1. 用数据确定过滤范围
先统计生产环境全部用户(约 5000+)的邮箱域名分布,而不是凭空猜测。结果显示:
- 真实占位域:
example.com(最多)、以及两个拼写错误变体(examle.com、example.c) - 大量真实的合作伙伴/外包公司域(不能误伤)
因此过滤规则用精确域名列表,只打击确认的占位域,不用宽泛正则(避免误伤真实外包用户)。
2. User 模型加过滤方法
DUMMY_EMAIL_DOMAINS= 精确占位域列表dummy_email_domain?(mail):纯逻辑判断(不看环境,便于单元测试)dummy_email?(mail):生产入口,return false unless Rails.env.production?(只在生产过滤;其它环境用占位地址是正常的,不过滤,避免影响现有测试)deliverable_emails(recipients):过滤收件人列表(去占位/空/去重)
3. 各 mailer 过滤
所有”发给用户邮箱”的 mailer(test_started / test_done / pending_test_canceled / ssh_link_not_ok / execution_result_finished / info_message / status_email / notify_user_canceled_job / send_debug_info)在 mail(to:) 前用 deliverable_emails 过滤,收件人为空则不发。
发给固定管理/配置列表的(都是真实地址)不过滤。
验证
- 单元测试覆盖纯逻辑方法 + 生产/非生产两种环境行为
- 预发布 console 模拟生产环境验证:占位邮箱被过滤、真实地址保留
- 生产部署后(以应用重启时间为界):之后 CI 持续运行下,退信彻底消失
设计权衡说明
- 过滤只在生产环境生效:其它环境用占位地址是测试惯例,不过滤以免影响现有测试
- 真实效果只能在生产观察(因为只有生产过滤);逻辑正确性由单元测试 + 预发布模拟保证
通俗版复述
这件事在解决什么?
换新邮局后,系统天天往外退信。查下来是:有个”机器人账号”(CI 自动化)一刻不停地跑测试,每跑完一个就想发封”完成通知”给它自己。但这个机器人账号登记的邮箱是个假地址(xxx@example.com,根本不存在)。老邮局比较宽松、加上当时退信也没人看,问题没显出来;新邮局很严格,一看是不存在的地址就退回来——于是测试一直跑、退信一直来。
怎么解决?
有两条路:一是把这个机器人的邮箱改成真的;二是干脆在发信前就把假邮箱过滤掉,不发给它。我们选了第二条,因为这样能一次性管住所有类似的假邮箱账号。
怎么保证不误伤?
先把生产上 5000 多个用户的邮箱域名统计了一遍,用数据确认哪些是假域名(example.com 和两个拼错的变体),哪些是真实的外包公司邮箱(不能误删)。所以过滤只精确打击那几个确认的假域名。另外,这个过滤只在生产环境生效——因为测试环境用假邮箱是正常的,不能一刀切。
结果:生产部署后,退信彻底不见了,正常用户的邮件照收不误。