Backend

先绑定邮箱还是先导入名单?操作顺序导致往来邮件无法同步的排查

1703 字约 6 分钟

摘要同样的导师 Excel 表,同样的网易邮箱账号,换新电脑时只是调换了“绑定邮箱”和“导入”的顺序,过去半年的往来记录就全部消失了。问题出在业务名单变化后,历史邮件没有被重新同步。

在 Auto Email Sender 中,有一项很受用户欢迎的功能:无论你之前是用手机 Outlook 还是网页版邮箱给导师发过邮件,只要在软件里绑定 IMAP 邮箱账号,它就会在后台同步,把每位导师的历史往来记录和回信情况还原出来。

某天,一位换了新 MacBook 的同学在群里求助:

“奇怪,我的账号没变,导入的导师名单也没变。为什么在旧电脑上能看到上周跟张教授的往来邮件,新电脑上张教授的记录全是空的?我点了好几次‘立即同步’,还是什么都刷不出来!”

我让他截了图,一步步排查他在新电脑上的初始化流程。

最后发现一个隐蔽的细节:

  • 旧电脑上: 先从导师名录导入了 200 位导师,然后在设置页绑定了自己的 163 邮箱;
  • 新电脑上: 先在设置页绑定了 163 邮箱(系统自动完成了首次同步),然后才点击“导入导师 Excel 名单”。

两组操作的数据完全相同,只是新设备初始化时的顺序颠倒了。结果过去半年的往来记录全部丢失了。

水位线没有错,问题在于它只记录了邮箱的状态

我们来看一下同步引擎的行为:

【新电脑上的时间线】
1. 用户绑定邮箱 ──> 系统触发 IMAP 首次全量扫描
2. 引擎拉取最近两个月的邮件 ──> 此时本地导师表是空的
3. 引擎判断:这些邮件都不是发给导师的(名单还没有导入)
4. 引擎把本地水位线 (High Watermark) 更新到最新一封邮件的 UID
5. 用户导入 200 位导师 ──> 导师数据入库
6. 增量同步再次运行 ──> 引擎发现“当前 UID 小于水位线,没有新邮件”,直接跳过
7. 结果:历史邮件全部位于水位线之前,之后再也不会被处理
通俗比喻:先翻相册还是先拿寻人启事

为什么操作顺序颠倒,记录就找不到了? 假设你有一本几千张照片的聚会相册(邮箱里的历史邮件):

  • 先导入名单,再绑定邮箱:你先拿到张教授的照片(导入名单),然后从头翻相册,找到了张教授的所有合影并做了标记;
  • 先绑定邮箱,再导入名单:你还没拿到照片,就先把相册从头翻了一遍,在最后一页夹了书签,写着“相册已看完,之后只看新拍的照片”。等朋友把张教授的照片给你时,你却说:“按规定,翻过的部分不再重看,除非有新照片。”

书签只说明相册翻过了,但当时你并不知道该找谁。

增量同步的水位线逻辑本身没有问题:UID 小于上次记录的邮件,都视为已经处理过,不再重复拉取。

问题在于,同步引擎只关注远端数据源(邮箱里有没有新邮件),没有考虑本地的关注范围(我们实际关心哪些导师)。

在第 2 步,系统确实“看过”了那些历史邮件,但当时本地没有导师数据。等导师数据导入之后,水位线已经越过了这些邮件,系统便不会再回头处理。

初始顺序同步时本地的导师集合历史往来的识别结果
先导入名单,后绑定邮箱包含 200 位导师历史邮件正常匹配
先绑定邮箱,后导入名单空集合历史邮件被当作无关邮件,之后永远不再处理
核心观点:往来记录是两端数据的交集

“通信记录”不是单独从云端拉下来的数据,而是远端邮件与本地关注名单相交的结果。 即使远端邮件没有任何变化,只要本地的关注名单改变了,之前的匹配结果也会随之改变。

清空水位线的做法为什么不行

发现问题后,很多人的第一反应是:“只要用户导入名单,就把水位线清零,重新跑一遍全量同步,不就行了?”

这种做法不可取。它会用一个新问题掩盖旧问题:

  • 一个老邮箱可能有近十万封跨越五六年的工作和生活邮件;
  • 用户今天导入 5 位导师,清一次水位线;明天通过爬虫抓取 10 位导师,又清一次;
  • 每次变动都会触发整个邮箱的全量重新遍历。IMAP 服务器很可能会封禁账号,软件也会因为持续的高 I/O 而卡死。
不要为了局部更新而重置全局基线

水位线是防止重复处理和过载的基础保护机制。为了几个新导师就清空水位线,反而会让系统更不稳定。

把导师名单的变化作为触发条件

正确的做法是:让同步系统同时关注两类变化,不仅响应“远端收到新邮件”,也响应“本地导师名单发生变化”。

我们梳理了所有可能让新邮箱进入同步范围的操作入口:

  • 从 Excel / CSV 批量导入导师;
  • 通过智能抓取页面保存单个或批量导师;
  • 手动新增或修改导师的联系邮箱;
  • 从回收站或归档列表中恢复导师。

只要有一个新的有效邮箱被加入关注名单,就向本地任务队列发送一个轻量的补扫任务:

[ 导师名单变化 ] ──> 提取新增邮箱 ──> 派发定向补扫任务 (历史窗口:近 2 年)
为什么修改姓名不会触发补扫?

只有“邮箱地址”或“有效状态(是否归档)”发生变化时,才会影响往来记录的匹配结果。如果只修改了导师的职称或办公电话,就不会触发补扫,也不会消耗网络资源。

单个导师与批量导入的不同处理

执行定向补扫时,需要考虑两种场景:

  • 场景 A: 用户在界面上新录入了一位导师;
  • 场景 B: 用户一次导入了 3,000 位计算机系教师的名单。

如果两种场景使用同一套逻辑,很容易出问题。因此我们对两种规模分别设计了处理方式:

【单个新增(少量)】
直接用该导师的邮箱向 IMAP 发起精确检索:
- SEARCH FROM "prof@univ.edu"
- SEARCH TO "prof@univ.edu"
通常几秒内完成。
 
【批量导入(大量)】
不逐个查询 3000 次,而是反过来处理:
- 先从服务器取回最近 2 年所有已发送邮件的 UID 列表;
- 批量拉取收件人 Header,在本地用 Set.intersection 计算交集;
- 只提取真正发过信的几十位导师,再针对他们的收件箱补充回信。

另外,每个补扫任务都与导师的“邮箱版本”绑定。如果任务排队期间,用户又把导师的邮箱改成了另一个地址,旧版本的补扫结果会被自动丢弃,避免脏数据被写入。

总结

这个问题给了我们几点设计上的启发:

  1. 不要只考虑单向的数据流: 在许多关联业务中,两端的数据都会变化。只监听服务端的增量,就可能漏掉客户端实体变化带来的影响;
  2. 操作顺序不应影响最终结果: 无论用户先做 A 还是先做 B,最终呈现的数据应该完全一致;
  3. 把业务变化抽象为事件: 不要把补扫逻辑分散在各个按钮的回调里,而应统一为“关注名单变化”这一事件,系统才能自动补全遗漏的数据。

评论