
你有没有过这种经历凌晨一点被客户电话吵醒对方在那边语气不善地说你们昨天上午发的那个EDI订单文件我们这边根本查不到。你打开运维终端翻出EDI平台的发送日志发送时间、传输状态都是正常的甚至收到了对方通信网关签发的确认回执。你对着日志解释了半天客户还是半信半疑。最后两边一核对才发现文件确实从你们的EDI服务发出去了也到了客户的EDI接收服务器但对方内部业务系统压根没有完成入库。这种假发送成功的事故在EDI运维里太常见了。做EDI时间长了你会发现我们真正需要的不是文件已发出的通知而是文件已成功通过EDI系统发给客户的可信证据。而这个证据怎么获取、怎么在第一时间让干系人感知到就是本系列要聊的话题。第一篇先讲最务实、投入产出比最高的方案邮件通知。1. 为什么需要监控已发送成功而非已发出大多数企业级EDI链路远比你想象的长业务系统生成文件 → 数据转换/映射 → EDI通信平台 → 网络传输 → 客户EDI服务 → 客户的内部业务系统。这中间任何一环出问题都会导致文件明明发出去了客户却什么都没收到的窘境。做互联网开发的朋友可能觉得奇怪HTTP请求发出去收到200响应不就完事了吗但EDI的通信方式不一样它更接近传统邮政加挂号信的模型。你寄出一封挂号信手里拿到的是我方已收件的收据不是收件人已经读完信的证明。文件从你的EDI服务器交到客户EDI服务器和文件真正进入客户ERP系统被业务部门使用是两件完全不同的事。邮件通知在这里的价值非常直接它在发送事件发生的瞬间把成功或失败的判定结果推送给需要知道的人而不是等技术员第二天上班后对着日志肉眼排查。对大多数预算有限、人力紧张的EDI运维团队来说邮件是最便宜、最好接入、也最好追溯的通知渠道。任何邮箱都能收邮件服务器也好对接不用额外装App不用协调客户侧的软件。这也是我把它放在系列第一篇的原因。1.1 一次假成功事故的真实还原拿我们团队之前遇到的一次事情举例。某个合作了挺久的物流平台客户突然打电话来说当天上午的入库单文件一条数据都没进系统。我们立刻查记录AS2通道文件发送成功对方网关的MDN确认回执也到了状态是processed一切看起来毫无问题。后来排查才发现客户那边在前一天升级了映射规则我们的文件版本没跟上对方EDI解析服务虽然收下了文件但在后续转换环节发现字段不匹配整批拒收了。而客户EDI服务发给我们的报错类回执按照旧规则被我们的监控程序当成了非关键消息忽略掉了。业务流程就这么断了一整天直到客户发现仓库数据异常才暴露出来。这个例子说明一个很残酷的事实任何一环的静默失败靠人盯是盯不过来的。如果我们当时有邮件通知至少业务跟单员能在第一时间看到文件传输状态成功但缺少业务级确认主动去找客户核对。所以从那以后我就下定决心把监控口径重新梳理了一遍。1.2 先分清传输成功和业务成功在动手做邮件通知之前团队必须对成功达成一致定义。我把成功分成两个层级传输成功指文件完整无损地到达了对方的EDI通信服务器。典型的标志是AS2的MDN回执、OFTP2端到端确认或者SFTP正常上传完成。这个层级的意义是网络通信没问题文件字节是完好的。业务成功指文件被对方业务系统实际接收并处理。典型的标志是对方返回的业务级确认消息比如X12体系下的997/999功能确认EDIFACT体系下的CONTRL消息或者是对方业务系统主动回传的某个单据编号。这个层级的意义是客户那边的业务流程真正往前走了。我遇到的大部分团队实际能把传输成功监控好就已经不错了。这没有错但必须在邮件通知里把层级写清楚——是已送达客户EDI网关还是已获客户业务确认。如果业务部门把这个搞混看到成功邮件就默认客户已拿到货单后面出问题就是扯皮。2. 定义成功不同协议下的确认机制与监控口径既然要监控成功第一件事就是搞清楚你用的EDI传输协议下什么才算成功。这里不存在统一答案不同协议的确认机制差异很大。我做了一张常用协议的对照表方便你直接对照自己的环境传输协议传输成功典型标志业务成功典型标志邮件通知建议口径AS2收到对方签名的MDN状态为processed收到997/999或业务回执文档以MDN为传输成功基准回执为业务成功基准SFTP / FTPS文件上传完成退出码为0字节数与源文件一致对方业务系统回执或人工确认以传输日志为基准主动询问业务回执OFTP2收到端到端响应EERP/SFNA业务级回执文档以EERP为传输成功基准VAN邮箱文件成功投入VAN邮箱VAN跟踪信息显示对方已提取以VAN状态跟踪为准提取不等于入库REST/API直连收到API的2xx响应对方接口返回业务结果以接口响应体里的业务状态字段为准很多工程师误以为只要MDN到手就万事大吉其实不对。MDNMessage Disposition Notification解决的是对方EDI服务已经收到并验证了这个文件的问题它只能证明传输通道是通的、文件是完整且签名有效的。至于客户EDI服务把文件扔进哪个业务队列、有没有成功调用下游系统写入数据库MDN一概不知道。所以我的建议是监控模块要把传输成功和业务确认分成两个独立状态位。传输成功立刻触发一次低打扰通知业务确认在超时窗口内到达再触发一次最终成功通知如果业务确认迟迟不来则触发传输成功但未获业务确认的预警。这样一套口径才能把前面说的假成功概率压到最低。2.1 先画一张成功判定矩阵不要急着写代码先用一张二维表把监控逻辑说清楚。横轴是每个待监控的交易伙伴纵轴是状态节点文件生成、传输发起、传输完成、收到技术回执、收到业务回执、业务回执超时。每个格子写上预期行为和异常行为。以AS2通道为例正常成功链路是文件生成 → 连接对方服务器 → 传输完成 → 对方生成并签名MDN → 我方验证MDN签名 → 对方业务系统返回997确认。监控模块每收到一个状态事件就推进一次状态机。邮件通知只在状态机到达关键节点时发出。这个矩阵看起来麻烦但它的价值在于它是团队与业务部门沟通的依据。把判定规则写在纸上和业务负责人过一遍对方同意按这个口径定义成功后面邮件通知发出去了才不会吵架。2.2 一个容易踩的误区收到MDN不代表客户已处理这已经是老生常谈但我每次还是要强调一遍。很多刚接触EDI的同事看到processed就兴奋地群发成功邮件客户那边却打来电话说系统里没有数据。我总结过原因大部分MDN的processed状态只代表EDI服务进程接收并初步验证通过如果客户在翻译器或映射环节配置了高安全级别校验文件完全可能在MDN发送之后、真正入库之前被拦截。这也是为什么我把997/999这类业务回执看得很重。在邮件通知设计里我会刻意避免用客户已收到这种字眼统一改成文件已成功送达客户EDI网关或者已收到客户业务系统确认回执。别看这两句话的差别微乎其微在意外发生做复盘时这两句话决定了你是背锅的一方还是主动发现问题的靠谱一方。3. 从发送日志到邮件自建通知模块的设计思路明确了成功的判定口径接下来就是怎么实现邮件通知的问题。我推荐的做法是不要改EDI系统本身在旁边搭一个旁路侦听模块。为什么这么做因为EDI平台通常承担着核心业务传输职能在上面加监控、改逻辑稍有不慎就会影响正常收发。很多EDI系统还是供应商封闭维护的你也不一定拿得到源码和接口文档。而旁路模块只读日志、只查数据库本质上不影响主线链路风险最小上线也最快。我见过有人直接在EDI平台的脚本编辑器里塞了一段Shell发邮件也能用但扩展性和可维护性都很差。旁路方案则是把事件采集、状态判定、通知触发做成独立模块后续哪怕要换成企业微信机器人或者短信告警代码改动范围都很小。3.1 事件源怎么选日志、数据库表还是API回调旁路模块首先要解决事件从哪来的问题。我按推荐优先级列举三种方式平台API/Webhook回调如果有最理想。EDI平台主动把发送记录推给你不依赖轮询实时性最好。可惜很多中小型部署没有这类接口。数据库表轮询稳定可靠前提是EDI平台的操作记录会落库。查询发送任务表关注状态字段和消息ID。缺点是高频轮询会给数据库带来负载通常设置10到30秒一次即可。日志文件解析不入侵业务库通过日志聚合工具或脚本读取平台日志。要重点处理日志轮转、时区戳和多样化的格式。这三种方式我都实际用过老实说大部分场景是数据库表轮询日志兜底配合。不是所有平台都有干净的结构化写入日志文件反而信息最全。3.2 一个旁路通知模块的骨架示例下面给一个简化但能跑的Python代码思路你可以照着改。它做的事情是轮询发送记录表找出状态变化的事件再按预先配置的规则决定发不发邮件。# monitor_edi.py —— 极简化示例用于说明核心思路 import time import smtplib from email.mime.text import MIMEText import pymysql def fetch_updated_records(last_id): # 查询发送记录表中大于 last_id 的新记录 sql SELECT id, partner_id, doc_type, file_name, transfer_status, biz_ack_status, send_time FROM edi_send_record WHERE id %s ORDER BY id # 这里省略实际连接池和查询参数按你平台表结构调整 return query_db(sql, (last_id,)) def decide_notify(record): # 判定矩阵只在关键节点发通知 # 传输失败 → 立刻告警 if record[transfer_status] FAILED: return (alert, 传输失败) # 大客户超时未收到业务确认 → 预警 if record[transfer_status] SUCCESS and record[biz_ack_status] TIMEOUT: return (warning, 传输成功但客户业务确认超时) # 传输成功且业务确认到达 → 发送成功通知可按伙伴过滤 if record[transfer_status] SUCCESS and record[biz_ack_status] ACKED: return (success, 客户业务系统已确认) return (None, None) def send_mail(subject, body, receivers): msg MIMEText(body, plain, utf-8) msg[Subject] subject # ... 连接你自己的SMTP服务器并发送 pass def main(): last_id 0 while True: records fetch_updated_records(last_id) for rec in records: level, desc decide_notify(rec) if level: send_mail(f[EDI通知][{rec[partner_id]}] {desc} {rec[file_name]}, render_body(rec, desc), get_receivers(rec)) last_id max(last_id, rec[id]) time.sleep(15) if __name__ __main__: main()这个骨架最关键的地方是decide_notify和get_receivers两个函数。前者实现第一节说的判定矩阵后者决定这件事该让谁知道。之所以用记录自增ID而不是时间戳做增量游标是因为数据库时间字段容易出现毫秒级误差导致漏读或重复读。用自增ID配合稍大一点的轮询间隔逻辑最简单也最稳。3.3 触发规则不是越多越好刚开始做邮件通知时我犯过一个错误把每个状态变化都当成重要事件结果一天能收到上百封邮件。不到一周团队里的人把邮件通知全部放进了过滤器真正出问题时反而没人看到。后来我把触发规则收敛成三条传输失败实时告警必须发给值班工程师和接口商务。传输成功但对大客户业务确认超时定时预警通常延迟30分钟后检查一次。关键交易伙伴的最终成功通知发给业务跟单员用于客户对账确认。其余的状态变化比如普通伙伴的成功通知、中间过程日志一律不打扰放在日报里统一汇总。做监控的第一原则是别让你的通知沦为新形式的噪音。4. 邮件模板与收件人策略一条通知该长什么样邮件通知做得好不好模板占一半。好的通知邮件应该达到一个标准收件人不用打开日志系统光看邮件就能判断发生了什么、影响多大、该找谁处理是不是需要马上管它。所以模板里的字段必须经过筛选不是把所有日志字段都丢进去。我一般保留以下这些字段交易伙伴名称和ID文档类型如ORDERS、INVOICE、855文件名发送时间带时区传输通道与消息ID传输状态业务回执状态文件大小和相关交易笔数建议动作邮件主题格式我固定为[EDI通知][交易伙伴A][传输失败] PO_20250617_0001.edi 2025-06-17 14:23主题里带上交易伙伴、状态、文件名和时间有两个好处一是收件人扫一眼就知道优先级二是手机邮箱的搜索过滤可以直接按主题建立规则。邮件正文的排版用纯文本就能满足绝大多数场景别整花哨的HTML。我把用过的一个模板简化如下你直接参考修改【EDI 交易结果通知】 交易伙伴某零售客户PRT3021 文档类型ORDERS采购订单 文件名PO_20250617_0001.edi 传输通道AS2 消息IDAS2-8f3d4e5f-20250617-001 发送时间2025-06-17 14:23:05UTC8 传输状态成功已收到对方MDN验证签名通过 业务确认未收到997功能确认 文件大小12.4 KB 交易笔数128 笔 说明文件已确认送达客户EDI网关但尚未收到客户业务系统 的功能确认回执。按监控策略此状态将在 30 分钟后复检 如仍未收到回执将升级通知。 建议动作请业务跟单员联系客户接口人确认内部处理情况。我特意在模板里把传输状态和业务确认分开列就是为了避免前面说的成功歧义。收件人时间长了也会养成习惯只有业务确认已收到这一行才是真正的落地成功。4.1 别把邮件发给所有人收件人设置如果偷懒把整个部门拉进一封邮件里长期下来一定会出事。ED运维邮件应该按角色和交易伙伴两个维度拆分。按角色分三条线技术线值班工程师、EDI管理员业务线跟单员、客服管理层对账、报表。传输类异常通知只发技术线业务确认类通知发业务线汇总报表发管理层。技术异常发给业务只会增加焦虑。按交易伙伴分更是关键。你同时对接了上游供应商和下游零售商A客户的收件人完全不想看到B客户的文件通知。我的做法是维护一份伙伴ID → 收件人列表的映射表动态渲染收件人。这样加一个新客户只需要改配置不用动代码。4.2 成功通知也得防疲劳成功类通知很容易被人忽视——因为大家默认成功是理所当然的。所以我只保留两类成功通知一是面向业务跟单员的关键伙伴最终入库确认用于驱动业务流程二是每日晚间的一次汇总把当天所有成功发送的文件单列其中方便业务和财务核对。其余普通成功事件不在实时通道打扰任何人。我自己在实践里发现一个额外的好处当成功通知被刻意压缩之后偶尔一次传输成功但业务确认超时的邮件反而会立刻引起重视因为它的频率低且落在预警通道里。这跟报警系统中降低基线噪音以提升异常灵敏度的逻辑是一个道理。5. 上线前后要处理的细节重试、时区、节假日与误报邮件通知表面看起来简单真正跑起来你会发现坑一个接一个。我在上线这套机制的初期踩过不少这里挑几个最值得说的。5.1 通知自身也可能失败别丢了事件最容易忽略的是SMTP邮件服务器本身也可能出问题。如果通知程序只是发完就忘邮件发送失败时发送事件也就悄悄丢了。所以我在旁路模块里加了一张notification_event表记录每个事件的判定结果、发送状态和重试次数。流程变成事件流转进来 → 写入通知队列表状态为PENDING→ 定时发送协程取PENDING记录 → 发送成功置为SENT失败保留消息并重试重试超过5次则升级为异常。这样即使邮件服务宕机两小时恢复后补发也能把积压的事件处理完。做监控的系统自己反而最不该裸奔。5.2 时区与业务日历决定通知是否及时跨境业务最头疼时区问题。客户在北美你的正常上班时间是对方的凌晨你以为早上上班看邮件刚刚好对方业务部门已经在处理昨晚的异常了。处理方案我总结为两点第一邮件正文里所有时间字段同时标注UTC和收件人所在时区避免误读第二区分实时告警和时段汇总夜间发生的成功事件不实时打扰合并到早上8点的摘要邮件里统一发出。对失败事件则保持实时告警哪怕凌晨也不能等。节假日也要考虑。外协的仓库在客户法定假日不收货但系统仍然会自动生成发送文件。遇到这种情况邮件通知照常发出去但收件人可能在度假看不到。所以我对有明确业务日历的伙伴配置了节假日日终汇总减少无意义的打扰同时确保开工那天有完整记录。5.3 上线前至少跑一周影子验证我强烈建议不要一上来就把邮件发给客户或业务部门。先跑一周影子模式旁路模块正常判定、正常生成邮件但只发给你自己的私有邮箱同时逐条和发送日志对账。这个阶段的目的是排查三类问题漏报有发送事件但没触发通知、误报不该发的结果发了、重复发送。发现漏报大概率是事件源解析不完整重复发送多半是游标或状态位没处理好。等连续两周对账无误再把收件人切到正式列表。对账有个小技巧在每封通知邮件里藏一个不可见的消息ID比如正文注释里的MSG-IDxxx后续任何争议都可以根据邮件ID反查数据库原始事件记录。这比我们群里互相问你昨天收到那封邮件了吗要靠谱得多。5.4 最小可用落地清单上面聊得比较散我整理了一份可以直接照着做的清单与业务方确认成功的判定口径是传输成功还是业务确认成功。确定事件源优先API回调其次数据库轮询最后日志解析。搭建旁路监控模块实现状态判定矩阵。编写邮件模板按协议分离传输状态与业务确认字段。配置收件人映射表和分级规则。加入通知事件持久化和重试机制。影子模式运行一周并逐条对账。切换正式收件人持续观察两周。固化日报汇总逐步关闭低频通知通道。这套流程走下来快则半个月慢则一个月就能把EDI发送成功监控的邮件通知跑稳定。它在技术上没有太高门槛难的是把成功的定义、通知的分级、收件人的策略想清楚。到了这个阶段你会发现最花时间的不是写发邮件的代码而是和业务、客户、运维各方确认什么样才算成功、谁需要被告知、什么样的通知不会变成新的噪音。这些想清楚了邮件通知这套方案就已经成功了一大半。我现在自己团队里跑的就是这个方案。跑了两个季度最大的体会是通知不是越多越好而是越准越好。把成功判定做实、把通知噪音压住邮件才能在关键时刻真正被人看到。后续我打算把同样的数据源延伸到发送量统计和失败趋势看板上让监控从事后追查变成事前预防这个就留到系列下一篇再聊。