云MAS短信接口对接:签名模板、回执与幂等封装实战
短信接口这个东西说简单也简单无非就是把一段文本推给平台的网关平台再往下发说麻烦也麻烦真正落到生产环境签名、模板、编码、回执、限流、幂等、重试一个环节没处理好就是线上事故。我这次要聊的是云MAS平台短信接口的完整对接过程——从账号申请到第一行代码跑通再到把它变成一个能扛住日发几万条的业务组件。如果你手上正拿着一份平台给的接口文档看得懂每个字段但不知道从哪儿下手或者你的系统已经接了短信但回执丢失、重复发送、内容超长这些坑一直没根治那这篇东西应该能帮你省掉几天摸索。我把整个链路拆成五个部分来讲包括接口形态选型、前置准备、报文细节、代码封装和故障排查中间穿插我自己踩过的坑代码和参数都可以直接拿去改改就用。1. 先搞清楚MAS短信接口到底是什么形态1.1 从业务代码到手机屏幕的完整链路很多人对接短信时脑子里只有调一个HTTP接口实际上从你的业务系统到用户手机中间至少横跨五六个环节。你的业务代码发起一次提交请求请求打到云MAS平台的前置机上前置机做鉴权、签名校验、号码合规和内容过滤过了这一关消息进入平台内部的发送队列队列按优先级和通道负载调度到具体的短信中心短信中心下发到用户所在的网络最后落到手机。成功后返回一个msgid失败后返回一个错误码。这条链路上任何一环卡住你在业务侧看到的都是短信没收到但原因可能天差地别。我习惯把这个链路画成一条责任分界线提交接口的成功只代表平台收下了这条消息不代表用户收到了。这是新手最容易误解的一点。平台返回提交成功时消息可能还在队列里排队也可能已经被下游拒绝真正的结果要靠状态报告回执来确认。所以一套完整的对接必须包含三个方向上行——你提交短信下行——平台回推状态报告反向——用户回复短信时平台把上行内容推给你。只做第一个方向等于开车不看仪表盘。理解这条链路还有个实际好处出问题时你能快速定位是哪一段的责任。平台返回码是0但用户没收到问题多半在下游通道或者号码本身的状态平台返回码是负数那就是你的报文有问题跟下游无关。这个判断能帮你省掉大量和平台侧来回扯皮的时间。1.2 HTTP短连接、长连接协议、SDK三种接入方式怎么选云MAS平台一般会同时提供几种接入形态文档里往往都列着但不会告诉你什么场景该用哪个。我按实际使用体验做个对照。接入方式典型形态适用场景主要缺点HTTP短连接POST表单或JSON一问一答日发送量万级以下业务分散每次建连开销大高并发下延迟抖动明显长连接协议私有二进制协议需维持心跳日发送量十万级以上要求低延迟实现复杂要处理断线重连和滑动窗口平台SDK平台封装的jar或动态库想快速跑通验证阶段版本绑定死出问题不好排查我的建议很直接除非你的日均发送量稳定在十万条以上否则一律先上HTTP短连接。理由不是HTTP更先进而是它的可观测性最好——请求报文、响应报文、耗时、返回码全是明文出问题抓个包就能看明白。长连接协议为了效率牺牲了可读性二进制帧解出来一堆字节排查成本高一个量级。而且短连接天然支持水平扩展业务量涨了加机器就行不用担心连接数分配。真正需要切长连接的信号有三个一是HTTP接口的QPS已经到了平台给你的上限加机器也没用二是你对下发延迟有硬要求比如验证码要求两秒内到达而短连接在高峰期建连就花掉几百毫秒三是你的发送量已经到了按通道包销更划算的量级。这三个条件不满足之前别为了看起来更专业去啃长连接协议性价比很低。还有一点要提醒有些平台的HTTP接口和长连接接口走的是不同的通道资源返回码体系和限流阈值都不一样。切换之前务必确认清楚别以为换了个接入方式其他东西都不变。2. 动手写代码之前必须搞定的事2.1 账号、签名、模板这三样东西的正确申请姿势很多人拿到账号密码就开始写代码结果调通了接口却发不出去回头一看是签名没备案。这三样东西的申请顺序和依赖关系得先理顺。账号分两种管理账号和发送账号。管理账号用来登录平台后台配置签名模板、查余额、看报表发送账号才是接口里用的那个通常带一个企业标识前缀。有些平台把两者合一有些严格分离申请时问清楚。发送账号还绑定了子端口号扩展码这个码会出现在用户收到的短信里也决定了上行短信回推到哪个地址别随便填。签名就是短信开头那个方括号比如【某某科技】。它必须提前在平台侧备案备案时要提供主体资质材料审核周期从半天到三天不等。这里有个高频坑签名的字数算在单条短信的长度里一个签名占6到8个字直接吃掉你十分之一的内容空间。所以签名别起太长四到六个字最合适既能识别又不浪费计费长度。另外签名一旦备案通过改动要走变更流程别在代码里硬编码一个还没批下来的签名去测试返回的会是签名不合法。模板是现在的主流做法。纯自定义内容的短信在很多场景已经被限制必须用报备过的模板加变量填充。模板申请时要写清楚变量位置和示例值审核方会看内容是否合规。模板ID拿到后代码里传的是模板ID加一组变量平台负责拼装。这里要注意变量长度模板正文加上变量实际值总长度不能超限变量太长一样会被截断或者拒发。提示签名和模板的审核状态是会变的平台可能因为内容调整策略而下线某个模板。生产环境里最好加一个启动自检把用到的模板ID列出来定期查一次状态别等发不出去才发现模板被停了。2.2 网络连通性白名单、端口、出口IP这三关MAS平台通常部署在专有网络里企业侧要接进去必须过三道关任何一道没通你看到的都是连接超时而且报错信息毫无指向性。第一关是出口IP白名单。平台侧会把你服务器的公网出口IP加进允许列表不在列表里的请求直接拒绝。这里最容易出问题的是服务器有多出口的情况——比如你的应用部署在容器里出口IP经过地址转换后和你在平台上登记的不是同一个。排查方法很简单在服务器上执行一次查询自己出口地址的命令拿到的结果和平台登记的对比。如果是集群部署所有节点可能的出口地址都要登记或者统一走一个固定的出口。第二关是端口放通。平台接口常用的端口是8080、9090这类非标准端口企业防火墙默认只放通80和443请求出去就被拦了。这一关的特点是现象诡异——telnet能通但请求超时或者偶尔通偶尔不通因为防火墙策略可能是按会话做的。建议直接用抓包工具在服务器上抓一次请求看SYN包有没有发出去、有没有收到回应一眼就能判断是本地拦了还是对端拦了。第三关是回调地址可达。状态报告和上行短信是平台主动推给你的这就要求你的服务有一个平台能访问到的地址。内网地址平台访问不到必须做地址映射。而且这个映射要稳定地址一变回执就全丢了。我见过有团队把回调地址配在一台临时测试机上上线后忘了改跑了一个月发现回执一条没有白白丢了所有送达数据。注意回调地址的连通性测试不要只测一次。平台侧推送往往有重试机制如果你的服务在重试窗口内一直不可达平台可能判定地址失效并降低推送频率后续即使恢复了也会延迟很久才补齐。2.3 编码与长度被低估的两个隐形炸弹编码问题在对接初期几乎必然遇到一次。MAS平台的接口对编码的支持不完全一致有的要求UTF-8有的要求GBK还有的接口提交用UTF-8但回执推回来是GBK。表现就是接口返回成功用户收到的是一串问号或者乱码。处理原则是以平台文档为准并且显式声明。请求头里带上字符集声明报文体按声明的编码序列化不要依赖默认值。如果平台要求GBK而你的业务系统是UTF-8在发送前做一次转码转完再算长度——因为GBK下一个汉字占2字节UTF-8下占3字节长度算法完全不同。这一点在判断内容是否超长时特别关键用错编码算长度会导致你以为没超实际超了平台直接拒发。长度规则本身也要吃透。单条短信的计费长度是70个字这里的字指字符数而不是字节数一个汉字算一个字一个英文字母也算一个字。超过70字自动拆成多条长短信每条按67字计算因为要占用几个字节做拼接标识费用按拆分后的条数收。举个例子一条含80个汉字的短信会拆成2条收2条的钱。签名也算在内。这个规则决定了你的文案设计能压到70字以内就压压不下去要算清楚成本营销类短信对成本非常敏感。还有个细节部分平台对长短信的拼接有开关关掉之后超过70字的部分会被直接截断用户收到的是半句话。上线前一定要用一条超长内容做一次实测看用户端收到的是完整两条还是被截断这个实验五分钟就能做完能避免后续大量客诉。3. 三个核心接口的报文细节拆解3.1 提交接口必填字段背后的校验逻辑提交接口是所有对接工作的核心字段看着不多但每个字段背后都有一层校验理解这些校验能让你少走很多弯路。下面是一份典型报文的字段说明不同平台版本会有差异但核心字段基本一致。字段是否必填说明常见坑account是发送账号用成管理账号会鉴权失败password是密码摘要通常为MD5大写32位大小写敏感部分平台要求加时间戳mobile是手机号多个用英文逗号分隔用中文逗号或分号会整体失败content是短信正文需含签名签名缺失会被内容审核拦截templateId视场景模板编号模板变量个数必须与模板定义一致sendTime否定时发送时间格式yyyyMMddHHmmss格式错会被当成立即发送或直接报错extend否扩展码/子端口影响上行回推路由msgId否业务侧唯一流水号不传则无法做幂等重复提交会重复发密码摘要这块值得单独说。绝大多数平台不传明文密码而是要求传一次摘要值。最常见的算法是把账号和密码拼接后做MD5取32位大写。有些平台还要求带上时间戳一起摘要防止重放。算法写错的表现是返回账号或密码错误但这个错误码和真的密码错误长得一样很容易误判。判断方法先用平台提供的在线调试工具或者官方示例代码跑一次确认摘要算法再往自己的代码里搬。别自己猜。mobile字段的批量能力要善用。多数平台支持一次提交多个号码上限从100到500不等。批量提交能显著降低请求次数和连接开销但要注意两点一是批量提交的返回是整体返回不区分单个号码的成功失败如果其中一个号码格式不合法可能整批失败二是部分平台对批量的大小有限制超过上限直接拒绝不做部分处理。所以批量之前先在本地做号码格式预校验把不合法的剔出去别一股脑丢给平台。msgId这个字段很多人不传我觉得是最不该省的。它是你做幂等和追踪的唯一抓手。生成规则建议是业务前缀加时间戳加随机位保证全局唯一。有了它你可以在本地建一张发送记录表提交前先查一下这个msgId有没有发过避免重试逻辑导致重复发送——这在验证码场景下是致命的用户收到两条不同验证码不知道该填哪个。一个最简的提交示例用HTTP表单方式curl -X POST http://平台地址:8080/sms/mt \ -H Content-Type: application/x-www-form-urlencoded; charsetUTF-8 \ --data-urlencode account企业发送账号 \ --data-urlencode password摘要值 \ --data-urlencode mobile13800000000,13900000000 \ --data-urlencode content【某某科技】您的验证码是1234565分钟内有效。 \ --data-urlencode msgIdBIZ20240101120000001 \ --data-urlencode sendTime响应一般是一个精简的文本或JSON包含返回码和msgid。返回码为成功时把平台返回的msgid和你自己的业务流水号一起落库后面回执匹配要用。3.2 状态报告接口回执匹配与丢失处理状态报告是平台主动推送到你配置的回调地址上的通常是一批一批推报文里包含msgid、手机号、状态码和时间。这个接口你必须自己实现一个接收端点而且要做到三个必须。必须快速响应。平台推送回执时有超时限制一般两三秒。如果你的处理逻辑里有数据库慢查询或者远程调用很容易超时平台会判定推送失败并重试重试几次还失败就可能停止推送。正确的做法是收到请求后立刻返回成功把报文丢进本地消息队列异步去做落库和业务处理。这个模式叫接收即返回是回执接收的标准做法。必须做幂等。平台的重试机制意味着同一条回执你可能收到多次。落库时用msgid加状态码做唯一约束重复的直接忽略别让一条回执触发两次业务动作。我见过一个场景回执重复处理导致订单状态被改了两遍虽然结果一样但日志乱成一团排查花了好久。必须处理匹配失败。回执里的msgid是平台侧的你需要在本地表里能查到对应的业务记录。如果查不到通常是两种情况一是你的落库比回执到达还慢这是典型的竞态问题处理办法是匹配失败时先落一张待匹配表过一会儿再补偿匹配二是回执里的msgid字段名或格式和你预期的不一致这个要提前和平台确认用实际报文验证。状态码的取值各家不同常见的有DELIVRD表示成功送达、UNDELIV表示未送达、EXPIRED表示过期、REJECTD表示被拒。这里的关键认知是未送达不等于没发出去可能用户关机、停机、号码空号、或者手机内存满。这些情况在业务侧要不要重发得按场景定。验证码类不重发因为用户可能已经通过其他方式获取了通知类可以有限重试但要设上限别把欠费号码天天发。回执丢失是另一个绕不开的话题。现实里回执到达率不可能100%能到95%以上就算健康。对于没有回执的消息业务侧不能无限等待要设一个超时兜底——比如24小时内没收到回执就按状态未知处理走人工或者降级渠道。这个兜底逻辑必须做否则你的发送记录里会永远躺着一批状态不明的数据。3.3 上行短信接口用户回复怎么接住上行短信是指用户回复你发出的短信平台把这个回复内容推送到你配置的上行地址。很多系统对接时直接忽略这部分觉得用不上但真到需要的时候——比如用户回复退订你没接住就会持续骚扰用户最后被投诉。上行报文一般包含用户号码、回复内容、接收时间、扩展码。处理逻辑比回执复杂一点因为要做内容解析和意图判断。最常见的是退订指令用户回复TD、退订、取消这类关键词你需要把号码加入黑名单。这个动作要快因为退订是有时效要求的处理晚了用户可能再投诉一次。扩展码在这里起关键作用。如果你用的是共享通道同一个平台上可能跑着你多个业务线的短信上行推过来时靠扩展码区分是哪条业务线发的。所以提交短信时一定要带上扩展码不然上行内容推过来你不知道该路由给哪个业务模块。上行还有一个容易被忽视的作用它是最好的内容质量信号。用户回复你是哪位“发错了”这类内容说明你的触达名单有问题回复怎么退订说明你的频次控制没做好。把这些上行内容做分类统计比看发送成功率更能反映实际问题。我在一个项目里就是通过统计上行内容发现某批号码是错误采集的及时止损。提示上行内容的解析不要做过度智能化的关键词匹配容易误伤。比如用户回复不要发了谢谢如果你的关键词是不要可能被误判成退订实际人家只是客气。稳妥做法是维护一个明确的指令词表只认表内的词表外的走人工复核。4. 把接口封装成能上生产的组件4.1 从裸调用到工程化四个必须补上的能力把接口调通只是起点真正能上生产还得补四样东西重试、限流、幂等、可观测。这四样缺一样跑一段时间就会出问题。重试要有边界。什么错能重试什么错不能得分清楚。网络超时、连接被拒这类传输层错误可以重试因为请求可能压根没到平台返回码是系统繁忙“队列满”这类临时性错误可以重试但号码格式错误“签名不合法”“余额不足”这类业务错误重试一万次也没用只会浪费资源。重试次数建议两到三次采用递增间隔别用固定间隔固定间隔在平台侧出现拥塞时会加剧拥塞。限流是保护自己也是保护平台。平台给你的账号有QPS上限超了会返回限流错误甚至临时封禁。本地要有一个令牌桶或者信号量控制并发阈值设在平台上限的七成左右留出余量。这个余量的作用是应对突发——比如某个业务模块一次性提交了大量消息限流器能把它削峰填谷而不是一股脑打过去被拒。幂等前面提过核心是业务流水号加本地去重表。这里补充一个细节去重表的过期时间要设不能无限增长。一般设24小时因为超过这个时间的重试已经没有意义了。清理任务用定时任务做别在请求路径上做删除会影响响应时间。可观测包含三块日志、指标、告警。日志要记录完整报文注意脱敏手机号中间四位打码、耗时、返回码、msgid一条日志一行方便检索。指标至少要有提交量、成功率、平均耗时、回执到达率、余额。告警阈值这样设成功率低于95%告警回执到达率低于90%告警余额低于三天用量告警。这几个指标能覆盖绝大多数异常场景。4.2 一份可以直接改用的封装代码下面这个封装用Python写结构简单重点是展示那几个能力的落点。实际用的时候按你的技术栈翻译就行逻辑是通用的。import hashlib, time, uuid, logging from datetime import datetime class SmsClient: def __init__(self, account, password, endpoint, qps_limit10): self.account account self.password password self.endpoint endpoint self.qps_limit qps_limit self.logger logging.getLogger(sms) def _digest(self): # 常见算法账号密码拼接后取MD5大写32位以平台文档为准 raw f{self.account}{self.password} return hashlib.md5(raw.encode(utf-8)).hexdigest().upper() def _gen_msg_id(self, biz_type): return f{biz_type}{datetime.now():%Y%m%d%H%M%S}{uuid.uuid4().hex[:6]} def send(self, mobiles, content, biz_typeBIZ, retry2): msg_id self._gen_msg_id(biz_type) payload { account: self.account, password: self._digest(), mobile: ,.join(mobiles), content: content, msgId: msg_id, } for attempt in range(retry 1): start time.time() try: resp self._post(payload) cost int((time.time() - start) * 1000) self.logger.info(sms_submit msgId%s cost%sms resp%s, msg_id, cost, resp) if resp.get(code) 0: return {ok: True, msgId: msg_id, platformId: resp.get(msgid)} if not self._retryable(resp.get(code)): return {ok: False, msgId: msg_id, code: resp.get(code)} except Exception as e: self.logger.warning(sms_submit_error msgId%s attempt%s err%s, msg_id, attempt, e) time.sleep(0.3 * (attempt 1)) return {ok: False, msgId: msg_id, code: RETRY_EXHAUSTED} def _retryable(self, code): # 只有临时性错误才重试业务错误直接返回 return code in (SYSTEM_BUSY, QUEUE_FULL, TIMEOUT) def _post(self, payload): # 具体HTTP实现略注意设置超时和字符集 ...这段代码里有三个设计点值得展开。第一_digest()每次调用都重新计算而不是在初始化时算一次缓存起来。原因是有些平台的摘要算法里带时间戳缓存会导致过期失败。如果你的平台摘要不含时间变量改成缓存能省一点CPU但差别可以忽略不如保持简单。第二_retryable()把可重试的返回码白名单化这是默认不重试的思路——只有明确知道可以重试的才重试其他一律返回。这个思路比黑名单安全得多因为平台新增返回码时黑名单会漏。第三日志里把msgId放在最前面方便按msgId搜索整条链路。4.3 并发控制和连接管理的一些细节HTTP短连接在高并发下的主要开销在TCP握手和TLS握手。如果平台接口是HTTPS每次请求都要完整握手延迟会明显上去。解决办法是复用连接用连接池。连接池的大小设成并发数的1.5倍左右别设太大太大反而会因为连接空闲被对端关闭而产生一堆无效连接。超时设置要分两段连接超时和读取超时。连接超时设短一点两三秒因为连不上就是连不上等太久没意义读取超时设长一点五到十秒因为平台在处理高峰时响应可能变慢读超时设太短会误判成失败然后重试反而加重平台负担。并发度怎么定先按平台的QPS上限反推。假设平台给你20 QPS单次请求平均耗时200毫秒那么并发数大致是 20 × 0.2 4 就能跑满设到6到8留点余量。这个算法比拍脑袋设个50要靠谱得多——并发数超过必要值多出来的线程只是在排队不提升吞吐还增加内存和上下文切换开销。批量提交和单条提交要分开处理。批量适合营销类场景一次几十上百个号码吞吐高单条适合验证码这类要求低延迟、需要精确知道每个号码结果的场景。别用一种方式覆盖所有场景那会逼着你在延迟和吞吐之间做不情愿的妥协。5. 排查实录那些文档里不会写的坑5.1 返回码速查与对症处理各平台的返回码定义有差异但错误类型高度相似。下面这张表是按错误性质整理的处理思路具体码值以你手上的文档为准。错误类型常见表现根因判断处理方式鉴权失败账号或密码错误摘要算法不一致或账号类型用错用官方示例验证摘要确认发送账号签名不合法内容审核不通过签名未备案或与备案内容不一致核对签名字符包括括号类型号码格式错误整批提交失败存在非法号码或用了中文逗号本地正则预校验剔除后重提内容超长拒发或截断编码用错导致长度计算偏差按平台编码算长度留出签名空间敏感词拦截内容审核不通过文案含平台词库内的词换表述别试图绕过余额不足提交全部失败账户余额低于阈值做余额监控提前充值频率超限间歇性失败超出账号QPS本地限流压到上限七成连接超时无返回码白名单、端口、网络问题服务器侧抓包定位这张表里我想多说两句敏感词拦截。很多人遇到拦截第一反应是找变体写法绕过去比如把字拆开、加符号、用谐音这是绝对错误的方向。平台拦截的内容通常是监管要求绕过拦截意味着你发出去的内容本身有问题后果比发不出去严重得多。正确做法是改文案或者和平台侧的对接人沟通确认词库边界。合规这条线不能碰。内容超长这个坑我再补充一个细节。有些平台的长度计算把签名也算进去有些不把签名算进去但发送时会自动加上导致你以为还有余量实际已经超了。判断方法很简单发一条正好70字的带签名内容看返回是成功还是超长一次实验就能确定规则。这种实验成本极低收益极高我建议每个新对接的平台都做一遍边界测试。5.2 三个真实场景的排查思路场景一提交成功率高但用户投诉收不到。这个组合说明问题不在你的提交环节而在下游。排查顺序是先看回执状态分布如果大量是未送达那就是通道或号码问题如果回执全是成功但用户说没收到那可能是用户手机拦截了或者号码归属地异常。第二步抽样看具体号码的回执明细找出是哪些号段集中出问题。第三步拿一个确定正常的号码做对照测试。这套流程走下来基本能定性是通道质量问题还是名单质量问题。前者要找平台侧要说法后者要清洗你的号码库。场景二回执一条都收不到但提交正常。九成是回调地址的问题。排查顺序先确认平台后台配置的回调地址是不是当前服务的地址特别注意有没有配成测试环境的然后用外部工具模拟平台推一条回执到你的地址看服务能不能收到最后看服务日志里有没有平台来源的请求记录。这三步能覆盖绝大多数情况。还有一种少见但坑人的情况你的服务前面有一层网关或者负载均衡它把平台的回推请求当成异常流量拦了因为请求的路径不常见或者请求体格式特殊。这种要看网关的访问日志才能发现。场景三短信内容出现乱码且只出现在部分号码上。这个现象很有迷惑性。乱码跟号码无关跟编码有关但为什么只出现在部分号码上答案往往是那些号码走了不同的通道而不同通道对编码的支持不一样。有些老通道只认GBK你发UTF-8过去它就乱码。解决办法是统一按最保守的编码发送或者在提交时显式指定编码参数并确保平台按此解析。如果平台不支持指定那就得找对接人确认各通道的编码能力做通道级的适配。这类问题排查起来最花时间因为现象不连续复现困难只能靠加日志把每次请求的实际编码和通道标识记下来攒够样本才能定位。5.3 成本优化与容量规划的实操心得短信是按条计费的量大了成本很可观优化空间主要在三个地方。一是控制长度。前面说过70字一条超了按67字拆。假设你的通知短信平均85字每条要收2份钱如果把文案压到68字以内成本直接减半。压缩技巧不是删信息是换表达把您的订单已经发货预计三天内送达改成订单已发货预计3日内送达少了一半字数信息量不减。这种文案打磨值得做尤其是高频通知类。二是合并发送。同一个用户短时间内收到多条通知能不能合成一条发这在电商、物流场景很常见。合并的前提是不影响用户理解且有时间窗口。实现上可以在应用层做一个短的聚合窗口比如5秒内同一用户的多条通知合成一条。这个优化的收益取决于你的业务形态通知密集的业务能省不少。三是名单质量。空号、停机号发出去一样计费部分平台按提交计费而非按送达计费所以名单清洗是纯赚的。定期用平台的号码状态检测能力筛一遍把无效号码剔除。这项工作的投入产出比通常很高尤其是历史积累的名单。容量规划上我习惯按峰值的三倍准备通道能力。峰值可能是日常的五到十倍比如大促当天。临时扩通道要走申请流程提前两周准备比较稳妥。另外别忘了余额大促前把余额充到预估用量的1.5倍因为大促期间充值流程也可能拥堵。注意余额告警的阈值不要设成固定金额要设成按最近七天日均用量计算的可支撑天数。固定金额在你业务量增长后会失效等到发现余额不足时已经晚了。6. 一些长期维护上的经验接口对接完不是结束是一段长期关系的开始。平台侧的接口版本会升级字段可能增减返回码可能调整这些变化通常通过公告或者对接人通知但你未必每次都能及时看到。所以我建议做两个自检机制。一个是契约自检。写一个定时任务每天用固定的测试号码发一条测试短信走完整链路验证提交、回执、上行三个方向都正常。这条测试短信的成本一天几分钱但能在平台侧发生变更时第一时间发现。任务要记录每次的耗时和返回码形成一条趋势线耗时突然上升往往预示着平台侧有变化。另一个是文档对账。每隔几个月把代码里的字段和平台最新文档对一遍看看有没有新增的必填字段或者废弃的字段。这件事很枯燥但能避免某天接口突然大面积失败时手忙脚乱。我见过有团队因为平台新增了一个必填的模板版本号字段导致所有模板短信失败排查了半天才发现是文档更新了而代码没跟上。还有一个经常被忽视的点发送记录的保留周期。短信记录包含手机号和内容属于个人信息范畴保留时间不宜过长按业务需要设一个期限到期做脱敏或者删除。这件事在合规检查时会被问到提前做好比事后补要省心得多。保留原始明文的需求通常是客服追溯可以把追溯窗口设短一些比如三个月超出窗口的只保留统计信息不保留明细。最后说一个心态上的事。短信这个渠道看起来古老但它的可靠性要求和合规要求比很多新渠道都高因为它直达用户且不可撤回。对接的时候多花点时间把边界情况想清楚比上线后被动救火要划算。我自己的习惯是每接一个新平台先花半天时间把所有返回码整理成表把每个码对应的处理动作写进代码注释后面不管是自己维护还是交接给别人都能少走弯路。