ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Notes邮件群发总翻车?服务器端路由与队列暗坑排查指南

Notes邮件群发总翻车?服务器端路由与队列暗坑排查指南

上周我又被 Notes 邮件群发的问题按在地上摩擦了一回。某客户的管理员跟我吐槽,说给 447 个外部地址发一封会议通知,自己已经很小心地把地址拆成了三批,每批 150 个,批次之间间隔五分钟,结果发完还是有人收不到,退信也没几封。我远程看了一眼他的操作,问题不在拆分批次,而在批次之间的连接状态、路由线程以及他压根没查服务器邮件队列。Notes 邮件群发在客户端界面上看着就是“新建邮件,填上地址,点发送”,可到了服务器端,一次群发是路由、队列、连接、投递这一整套链路在协作。今天这篇文章不聊客户端排版技巧,主要聊那些让群发翻车的服务器端暗坑,希望下次你再遇到“收件人收不全”的时候,能少走点弯路。

1. 群发不是把抄送栏拉满那么简单

1.1 我处理过的那次“三百封邮件半夜才到”

先讲一个典型的案例。有一回某公司行政给全员发节日通知,收件人其实都是内部 Notes 用户,一共三百多人。发件人在客户端里点了发送,也看到“已发送”里有了记录,以为事情结束了。结果第二天早上陆续有人反馈:我怎么昨晚十一点才收到?还有人到第二天上班打开 Notes 才看到邮件。

我上服务器控制台看了一下,Router 任务确实在运行,但邮件队列里的积压数量非常吓人,而且队列里的消息基本都是同一封通知。再看服务器配置,问题就清楚了:管理员为了让群发更快,把路由线程数调成了 16。十六个线程听起来很猛,但服务器到内部域用户的路由并不需要那么多并发,反而每个线程都在抢占内存和磁盘 I/O,队列处理出现抖动,于是一部分消息被反复延迟。

这个案例说明一个朴素的道理:在 Notes 里点发送只是开始,服务器端的路由器要逐条处理收件人、构建投递路径、写队列。群发邮件一旦数量上了三位数,你面对的就不再是“写邮件”问题,而是“服务器批量投递”问题。

1.2 先分清三类群发故障:收不全、退信、延迟

一听到“群发有问题”,很多人第一反应就是去看退信。但我在实际运维里遇到的群发故障,大多数可以归成三类,每一类的排查路径完全不同。

故障表现最常见的根因第一排查方向
收件人收不全,但发件人没收到退信对方反垃圾网关静默丢弃、收件人地址无效但服务器未回弹、队列积压后消息被丢弃查看服务器 MAIL.BOX 队列、外部网关日志、抽样询问收件人
收到“无法投递”的退信收件人地址写错、对方服务器拒绝、发件人身份校验不通过看退信里的 SMTP 状态码,提取被拒绝的地址,批量核对
邮件延迟数小时甚至次日才到网络带宽不足、路由器线程配置过高、连接文档并发限制、对方服务器限流看路由统计、连接文档、队列中的投递时间戳

我见过不少人把这三类混在一起排查:明明是延迟问题,却去检查收件人地址;明明是身份校验被拒,却去调整路由线程。所以我每次接到类似问题,都会先问一句:到底是不全、退信,还是晚到?把症状确认清楚,再动手查原因,比盲目改配置靠谱得多。

2. 收件人数量卡点:Notes 究竟怎么计算一次发信的代价

2.1 收件人上限不是拍脑袋,是内存与路由的平衡

很多管理员喜欢问一个问题:Notes 一次群发最多能发多少人?说实话,这没有一个放之四海而皆准的数字,但你可以从机制上理解为什么不能随便拉满。

当一封邮件带着大量收件人进入路由器时,路由器要做的事情比普通人想象的多。它要先解析每个收件人的地址,判断收件人属于内部用户、群组还是外部地址;如果是群组,还要展开群组成员;展开之后可能还有嵌套群组,必须一层层递归下去。这个递归过程会把收件人块在内存中成倍放大,邮件大小在服务器端也会膨胀。更要命的是,外部邮件网关通常有“单封邮件最大收件人数”的限制,很多环境设在 50 到 100 之间,超过这个数量对方网关可能整封拒绝,而不是只拒绝超出的那部分。

我自己在实际部署中的经验是:发给外部地址的群发邮件,单封收件人控制在 50 人以内容易被对方接受;发给内部用户时,如果管理员不希望邮件队列出现长队,也尽量控制在一百人以内。超过这个量,就老老实实拆批。

2.2 收件人、抄送和密送都会占用“名额”

有一类故障特别隐蔽:发件人明明只填了 80 个收件人,结果对方网关提示单封邮件收件人过多。我排查了很久才发现,问题出在抄送和密送栏。

Notes 客户端里,收件人、抄送、密送是三个不同的字段,但在服务器端构造邮件消息时,它们最终都会被合并进收件人地址块。也就是说,你填 50 个收件人、20 个抄送、15 个密送,实际收件人总数是 85 个,而不是 50 个。很多人潜意识里觉得“密送是隐藏的,大概不会计入服务器负载”,这是一个非常危险的想法。

尤其要注意的是嵌套群组。如果收件人里放了一个群组,群组里又套了另一个群组,展开后的实际人数可能远超你看到的群组名称数量。我在排查一个案例时,发件人以为自己只发给了 12 个群组,结果展开后是 600 多人。所以群发之前,先确认收件人总数,而不是确认填了几个地址。

2.3 分批发送的参照节奏与间隔

既然要拆批,怎么拆才合理?我常用的做法是:每批不超过 50 个收件人,批与批之间至少间隔五到十分钟。

这个间隔是有讲究的。每一批邮件发出时,服务器会和外域网关建立一次或多次 SMTP 会话。如果批次间隔太短,上一批会话还没结束,下一批又开始建立新会话,双方服务器可能因为并发连接数过多而互相等待,甚至触发对方的限流策略。间隔五到十分钟,目的是给上一批的会话留出完整的关闭和确认时间。

如果你发的邮件带有大附件,间隔还要拉长。我曾经给一个客户设置过 30 分钟间隔的大附件群发,虽然不是最快,但整个投递过程几乎没出现失败。记住一个原则:群发求稳不求出快,邮件系统主要是可靠性优先。

3. 路由线程与 MAIL.BOX:真正挤住群发时间的隐形瓶颈

3.1 路由器线程数:改多了反而更慢的原因

Notes 服务器里的 Router 任务,负责把邮件从本地路由到目标服务器或外域。很多管理员遇到群发慢,第一反应就是调高路由线程数,觉得线程越多并发越大。这个方向不是完全错,但很容易调过头。

Router 线程数调高,确实能让更多消息同时处于路由处理状态,但它不是无代价的。每个线程都需要内存、文件句柄和 CPU 时间片;而且目标连接文档或外部网关往往有并发连接限制。比如对方只允许八个并发连接,你本地开了十六个线程,多出来的八个线程只能在排队,反而增加了消息在队列里的平均等待时间。

我一般把 Router 线程数控制在二到四之间;如果确实要群发大量消息,临时调到六到八可以,但用完之后要记得改回来。调线程数之前,先看一眼show tasks确认 Router 进程状态,再看连接文档里的并发限制,否则调了也白调。

3.2 群发大附件时的“上午发、晚上到”全过程

有一种延迟属于物理算术题:邮件太大,带宽不够。听起来像废话,但很多人真的不会提前算。

假设你群发 500 个外部收件人,附件 20MB。这不是服务器存一份然后大家共享,而是每个收件人的投递都会传输一份完整的 20MB 数据,外发总量就是 20MB 乘以 500,约等于 10GB。如果你们的上行带宽是 10Mbps,换算下来每秒约 1.25MB,这 10GB 需要 8000 秒,也就是两个多小时。这还是在网络稳定、没有重试、对方完全配合的前提下。

所以当你看到“上午发出去,晚上才到”的群发记录,不用急着怀疑路由器配置,先算一算数据量。很多客户的实际带宽远没有想象中那么高,上传 20MB 附件可能就要几分钟,群发后邮件队列长时间不下降,多半就是这个原因。

建议是:大附件群发前,要么压缩文件,要么把附件传到内部共享服务器,然后在邮件正文里放链接。如果附件确实不能省,那就把收件人拆成更小的批次,并且在非高峰时段发送。

3.3 用路由统计和邮件队列定位堵点

排查群发延迟时,直接看服务器控制台比看客户端界面有用得多。我常用的命令是几个:

show tasks show stat router tell router show queues

show tasks用来确认 Router 是否在运行,有时候 Router 任务因为异常退出了,但邮件不会消失,只是没人投递。show stat router能看到路由器处理过多少消息、当前有多少失败、平均传输耗时等关键指标。tell router show queues能列出 MAIL.BOX 里积压的消息数量和目标服务器,这是判断堵在哪一跳的最直接方式。

如果队列堆积在发往某个外域的通道上,问题大概率出在外域网关或连接文档;如果队列堆积在内部服务器,那就要看目标服务器是否停机、磁盘是否满、MAIL.BOX 是否已经膨胀。很多人忽略了一点:MAIL.BOX 本身也是数据库,它也会损坏、会膨胀,需要定期维护。群发大邮件时,MAIL.BOX 的物理文件大小会迅速增长,如果服务器磁盘空间不足,邮件就会一直卡在队列里并反复报错。

4. 退信、拒收和身份校验:群发失败时最容易被蒙在鼓里的环节

4.1 为什么失败通知书发不到你手里

群发最怕的不是收到退信,而是什么都收不到。因为收不到退信,你无法判断邮件到底是被拒收、被丢弃,还是已经进了对方垃圾箱。

退信发不到发件人手里的原因有好几种。第一种,发件人地址写成了noreply@域名这类没人看的地址,系统把退信发过去了,但没人处理。第二种,邮件服务器自身有垃圾过滤机制,把含有“Message undeliverable”字样的退信误判为垃圾邮件,直接丢进了发件人邮箱的垃圾文件夹。第三种,对方邮件网关在反垃圾策略下选择“静默丢弃”,也就是说它判断这封邮件有问题但不给发件方回任何错误码,这种情况你永远等不到退信。

所以我处理群发问题时,会把“是否收到退信”当作参考,而不是结论。真正的结论要靠队列记录和抽样调查去验证。

4.2 发件人身份、邮件安全策略与“退信地址”的连带反应

现在外部邮件系统越来越看重发件人身份。很多企业的群发邮件用的是service@、notice@这类专用地址,但如果发件域没有配置相应的发件人身份校验策略,对方网关会直接拒绝接收,而且可能不返回明确原因。

典型现象是:来自你的 Notes 服务器的邮件,一部分外部用户能收到,另一部分被退信,退信内容写着“疑似垃圾邮件”或“发件人身份验证失败”。问题往往不在收件人地址,而在发件人地址所在的域没有做正确配置。

如果你没有配发件人相关策略,建议先补上;同时保证服务器投递外域时,发件人地址域名与服务器的实际身份一致。发件人域不一致时,收件方反垃圾网关会认为邮件在伪造身份,轻则进垃圾箱,重则直接拒收。这也是为什么群发专用的no-reply地址看似方便,实际坑最多。

4.3 三个真实案例里的排查顺序

我挑三个自己处理过的案例,给你一个排查思路。

案例 A:给一批客户发定向通知,结果整批邮件被对方邮件网关全部拒绝。我先把退信的状态码提出来,发现统一是对方拒绝接收。接着我没有急着改服务器配置,而是先拿一个普通测试账号从同一台服务器给对方的个人邮箱发了一封测试邮件,结果同样被拒。这就把范围缩小到发件方身份或服务器 IP 身上,最后确认是发件人域缺少校验配置。

案例 B:发件人邮箱里没有任何退信,但收件人反馈比例很高。我上服务器查了tell router show queues,发现队列早就清空,说明服务器认为投递成功了。再抽样检查发现,被漏掉的收件人全是同一个外部域,而那个域恰好对单封邮件收件人数量设置了上限,整封消息被对方网关在入口处丢弃,且不回退信。

案例 C:一个群组地址发送时,展开后混入了一个已经离职员工的地址,导致该域名投递失败。由于其他收件人都收到邮件,只有那个地址失败,系统生成了一条失败报告,但管理员没有看自己的邮箱,一直以为群发成功。后来把群组清理了一遍,问题才彻底解决。

这三个案例给我的共同经验是:排查顺序应该先看退信和邮件头状态码,再查服务器队列和路由统计,最后再检查发件人身份和收件人列表。别一上来就动配置,先把现象和日志对齐。

5. HTML 正文与图片附件:群发时最影响观感和性能的细节

5.1 内嵌图片与引用图片:群发场景下差距巨大

很多群发邮件喜欢把签名里的 Logo、正文里的示意图直接插入 Notes 邮件正文。客户端里看着很美观,但一旦群发,这种“内嵌图片”会变成群发性能的隐形杀手。

内嵌图片会被编码进邮件正文的 MIME 部分,每封邮件都要携带一份完整图片。假设一张图片 500KB,群发两百人,邮件总流量就增加了 100MB。如果图片是从网页复制过来的,实际可能更大。更麻烦的是,图片会推高单封邮件的体积,当收件方邮件系统有单封邮件大小限制时,整封邮件都会被退回。

我的建议是:群发邮件的图片优先使用“引用图片”方式,也就是图片放在有公共访问权限的 Web 服务器上,邮件正文里用链接指向它。如果必须内嵌图片,先把图片压缩到合理范围,一般每张控制在 100KB 以内。另外,很多人不知道 Notes 邮件签名里的图片也会被算入邮件体积,群发前记得检查签名里有没有大图。

5.2 中文邮件头、字符集与 MIME 编码的坑

群发邮件出现中文乱码,发件人客户端里是看不出来的,因为 Notes 客户端自己解析自己的格式,和外部邮箱解析 MIME 的路径完全不同。这种问题只能通过外部邮箱做真实测试。

乱码常见原因有两个。第一个是 HTML 正文没有声明字符集,或者声明与邮件实际编码不一致。你手工拼 HTML 时,一定要在<head>里加上字符集声明,比如 charset=utf-8 或 gb2312。第二个是主题和发件人显示名里的特殊字符,在 SMTP 传输过程中被错误编码,收件方看到的主题就是一堆问号。

我处理过的实用方法是:在正式群发前,先给自己常用的外部邮箱发一封测试邮件,用 Outlook、Foxmail 或者网页邮箱各看一次。不要只在自己的 Notes 客户端里预览,因为 Notes 客户端能正常显示不代表外部客户也能正常显示。

6. 群发前的复查清单:值得打印出来贴在工位上

6.1 服务器配置该检查哪几项

群发前不要只盯着收件人列表,服务器端的几个配置项也应该例行检查。我一般会按下面这个顺序过一遍:

1. Router 任务是否正常运行 2. 服务器配置文档里的 SMTP 限制和收件人相关策略 3. 连接文档里的并发连接限制是否合理 4. 服务器磁盘空间是否充足,MAIL.BOX 是否有维护计划 5. 目标域的特殊限制,比如对方单封邮件最大收件人数

这些配置不是每次都要改,但至少要心里有数。尤其是连接文档,它决定了你的服务器到目标外部网关可以用多少个并发会话,这个数值如果被设置成 1,群发大批量邮件时会被硬生生拖成串行投递,慢到你怀疑人生。

6.2 收件人列表的清洗与去重

收件人列表的清洗是群发里最不起眼但最重要的一步。我的习惯是:先把收集到的地址统一格式,去掉首尾空格;再用文本编辑器的排序功能去重;检查是否存在同一个地址出现在收件人和抄送里;最后展开所有群组,确认无嵌套群组、无停用账号。

如果你是从 Excel 或 CSV 导入地址的,要特别注意那一列有没有隐藏空格或不可见字符。这些字符在 Notes 客户端里看不出来,但发送时会导致地址解析失败,生成大量无意义的退信。清洗完毕后,最好导出成文本文件再核对一遍总行数。

6.3 先发测试群再做总量控制

即使你的收件人列表已经清洗过,也不要直接对全部收件人发起群发。正确做法是先挑五个到十个测试地址,这几个地址最好包含你熟悉的外部邮箱和不同邮件系统,发一封与正式内容完全一样的测试邮件。

测试的目的不只是看乱码和排版,还要记录发送和到达的时间差。如果测试邮件五分钟后对方还没收到,就不要继续往下发,因为正式群发会放大同样的问题。测试通过后,再按我之前说的分批节奏,小步快跑地往外发。

6.4 发完以后怎么确认邮件真正送达

邮件点完发送不等于结束。我习惯在群发结束后十五到三十分钟再看一次服务器队列。如果队列已经清空,通常说明所有消息已经从本地服务器成功投递出去了。但“投递到对方网关”不等于“收件人真实收到”,中间还可能经过对方内部的垃圾过滤和路由,所以抽样询问收件人仍然是最笨也最有效的方法。

你也可以利用 Notes 的邮件追踪功能,输入发件人、收件人和时间段,查看每封邮件的状态轨迹。需要注意的是,追踪信息一般只维护一段时间,群发后如果要留证,最好在当天完成查询并记录结果。

说实话,Notes 邮件群发翻车的原因翻来覆去就那几类:收件人数量没有控制好、路由线程配置不当、附件体积没算账、发件人身份没过关、收件人列表里有脏数据。我在实际运维里把这些点整理成了一张复查单,每次群发前照做一遍,群发出问题的概率明显下降。如果你现在正被群发问题困扰,建议先从邮件队列和路由统计看起,不要一上手就改服务器参数,很多时候问题并不在配置,而在你看漏的那批收件人。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进