☰
阿里云短信服务全流程实战:从签名审核到回执追踪
2026/10/14 2:43:30 网站建设 项目流程

短信服务这块,我陆陆续续接手的项目里几乎都绕不开验证码、通知、营销类消息。一开始大家都在群里喊“短信发不出去”“审核怎么又失败了”“用户收不到验证码”,问题五花八门。踩过几次坑之后,我把自己做阿里云短信业务的完整流程整理了一遍,从账号配置到代码落地再到回执追踪,一篇讲清楚。这篇文章适合后端开发、独立开发者和刚接手短信业务的运维同学,照着做基本能少走一大半弯路。

1. 业务场景与整体设计

1.1 短信在业务里到底扮演什么角色

短信服务最核心的价值只有一个:触达确定性。跟站内信、App推送、微信模板消息比,短信不需要对方在线、不需要下载客户端、不需要关注公众号,只要手机号能用就能收到。所以它在业务里通常承担三个角色:

  1. 安全验证:登录验证码、注册验证码、找回密码、支付确认、设备绑定。这类场景对到达率和时效要求最高,通常要求秒级送达。
  2. 状态通知:订单发货、物流更新、预约提醒、工单处理结果。这类消息对延迟没那么敏感,但需要保证不丢、可查。
  3. 营销触达:活动促销、优惠券提醒、召回唤醒。这类场景发送量大,对成本敏感,而且受运营商通道限制最严格。

我做过的模拟项目X里,短信主要用在登录验证码和订单通知上。登录验证码走的是“发送即焚”策略:存缓存、设过期、定时清理。订单通知则更多依赖回执追踪来确认用户真的收到了。

1.2 为什么选阿里云短信而不是自建通道

早期有些团队会自建短信网关,或者直接找第三方短信代理(不是网络代理,是通道代理商)接入。自建网关意味着要跟运营商谈通道,个人和中小公司基本没这个能力,通道质量、到达率、审核规范全都是坑。第三方代理质量参差不齐,有的价格低但到达率感人,有的到达率还行但接口不稳定,出了问题连个技术支持都找不到。

阿里云短信比较稳的点在于:

  • 接口标准:国内站API版本固定,SDK多语言齐全,有什么问题查文档、提工单都方便。
  • 通道质量:三网(移动、联通、电信)覆盖,发送高峰期基本不会因为通道拥塞而大面积延迟。
  • 合规兜底:签名、模板审核机制帮你把内容合规这个问题提前卡住,至少在平台侧不会出现明显违规的发送。
  • 成本透明:按条计费,签名和模板审核免费,发起营销活动前可以估算成本。

从个人开发者到企业项目,阿里云短信基本是“先用起来再说”的最优解。等业务量大了、有特殊通道需求了,再考虑跟运营商直连或者接入多厂商容灾也不迟。

1.3 最小可上线的短信业务架构

先别一上来就把MQ、分布式事务全安排上。一个真正最小可用的短信业务,其实只需要四块:

  • 接入层:业务服务(登录/注册/下单)调用短信服务模块。
  • 缓存层:存验证码、控制发送频率、做幂等。
  • 发送模块:封装短信API调用,统一管理签名、模板、参数。
  • 回执模块:接收短信状态报告,做失败重试和对账。

如果团队刚起步,验证码直接存Redis就行。回执可以先用HTTP回调,不用急着上消息队列。只有当发送量大了、多环境多应用都要消费回执的时候,才考虑用队列解耦。

这个架构的关键点在于:发送和业务解耦。不要把短信API的细节散落在各个业务代码里,后面要加缓存、限流、统计的时候,没有统一出口会非常痛苦。

2. 前置工作:账号、权限、签名与模板

2.1 RAM子账号与AccessKey的最佳实践

很多人一上手就用主账号的AccessKey调短信接口,图省事。这个习惯我看到一次就想拦一次。主账号AK权限是全部资源,一旦泄露,不只是短信费用被刷的问题,整个云账号的资源都可能被操作。

正确做法是在RAM控制台创建一个子账号,只授权短信服务的权限。最小权限策略里,常用的权限点大概包括:

  • dysms:SendSms:发送短信
  • dysms:QuerySendDetails:查询发送详情
  • dysms:QuerySmsSignList:查询签名列表
  • dysms:QuerySmsTemplateList:查询模板列表

我一般还会在代码里给AK单独做配置管理,不写死在代码仓库里,本地开发走环境变量,线上走密钥管理服务。AK泄漏之后第一时间去RAM里禁用、轮换,这个流程也必须在文档里写明。

2.2 签名申请的类型与材料

短信签名相当于你的“发件人名称”,用户看到短信时最先注意到的就是它。签名申请要按实际业务场景选类型:

  • 网站:需要已备案的网站域名
  • A需要在应用商店有上架记录,提供应用名称和商店截图
  • 公众号或小程序:需要公众号或小程序认证截图
  • 企业:需要营业执照等企业资质

签名内容通常是业务品牌名或网站名,比如“某电商平台”“某应用提醒”。个人申请时如果签名跟账号主体资质对不上,很容易被驳回。

我踩过的坑是这样的:最早给模拟项目X申请签名时,名字里带了“验证码”三个字,结果被驳回。官方要求签名不能包含“测试”“验证码”这类通用词,签名要代表实际业务主体。后来把签名改成业务名称才通过。

2.3 模板编写规范与审核要点

模板是发送内容的大纲,写法直接影响审核效率和发送成功率。模板里如果有变量,需要明确变量名和示例,比如验证码模板:

【模X提醒】您的验证码为${code},请在${time}分钟内有效。为保障账户安全,请勿泄露给他人。

模板审核最容易被驳回的几个点:

  1. 变量内容问题:变量不能传网页链接、IP地址、微信号、手机号、QQ群等。模板里写“${url}”让用户去点击,基本必驳。
  2. 内容敏感词:营销类模板里带促销、优惠幅度、加群等敏感字眼,会被要求修改措辞。
  3. 缺少退订说明:营销短信必须带“回复TD退订”这类字样,否则审核不通过。这一点很多新手会漏。
  4. 模板与原签名不匹配:签名是A品牌,模板内容却是B业务的,会被判定为冒用。

模板审核不通过时,控制台会给出具体的驳回原因,照着改一般能过。不要反复提交一模一样的模板,系统会有记录,重复提交反而更慢。

2.4 常用的测试手段

正式发送之前,建议先在控制台或者用测试签名、测试模板调试。阿里云短信控制台里可以申请“测试签名”和“测试模板”,限定测试手机号使用,它不会真的下发到普通用户,也不会扣费。用这套东西跑通整个链路之后,再申请正式签名和模板。

如果项目比较急,可以先临时用一个已经通过审核的通用模板做开发联调,等业务自己的签名、模板下来之后再切换。但要注意,线上切换前一定要发真实手机号验证一遍,别想当然以为“接口能通就行”。

3. 发送实操:API调用与代码落地

3.1 请求参数逐个拆解

发送短信的核心接口是SendSms。这里把关键参数拆开看:

参数类型说明
PhoneNumbersString接收短信的手机号码,支持一次发给多个,最多建议一次1000个
SignNameString短信签名名称,来自控制台审核通过的签名
TemplateCodeString模板CODE,形如SMS_123456
TemplateParamString模板变量对应的JSON串,例如{"code":"123456"}
SmsUpExtendCodeString上行短信扩展码,一般用不到
OutIdString外部流水扩展字段,方便业务侧关联查询

TemplateParam是新手出错率最高的地方。它是一个JSON字符串,如果模板里有${code}、${time}两个变量,那这个参数必须像这样写:

{"code":"123456","time":"5"}

注意JSON串本身在传参时是字符串,不要转义多一层。很多“发送失败、变量不匹配”的问题都是这里出的。

3.2 Java SDK发送验证码的完整代码

下面是我在实际项目里跑通的Java写法,用的是通用API请求方式,只依赖core包,适用于大部分SDK版本:

import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; public class SmsSendUtil { private static final String REGION_ID = "cn-hangzhou"; private static final String ACCESS_KEY_ID = System.getenv("ALIYUN_AK_ID"); private static final String ACCESS_KEY_SECRET = System.getenv("ALIYUN_AK_SECRET"); public static boolean sendVerifyCode(String phone, String code) { try { DefaultProfile profile = DefaultProfile.getProfile( REGION_ID, ACCESS_KEY_ID, ACCESS_KEY_SECRET); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "模X提醒"); request.putQueryParameter("TemplateCode", "SMS_123456"); request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); request.putQueryParameter("OutId", "verify_" + System.currentTimeMillis()); CommonResponse response = client.getCommonResponse(request); String data = response.getData(); // 这里用JSON解析data,判断"Code"字段是否为"OK" // OK表示SendSms请求已受理,但不代表短信一定送达 return data != null && data.contains("\"Code\":\"OK\""); } catch (Exception e) { // 记日志、做异常告警 return false; } } }

这里要特别强调一个误区:SendSms返回Code=OK只是说明阿里云接受了你的发送请求,不代表用户已经收到、更不代表用户能看到。真正确认送达必须看短信状态报告。

3.3 响应解析与BizId的作用

发送成功后,返回值里会带一个BizId字段,它是这次发送请求的唯一业务标识。后续用QuerySendDetails查询发送详情时,需要把BizId传进去。所以发送成功之后,建议把手机号、签名、模板、BizId、请求时间都存一份日志或记录表,后面排查“用户说没收到”时全靠它。

下面是用QuerySendDetails查询详情的简化示意:

CommonRequest queryRequest = new CommonRequest(); queryRequest.setSysMethod(MethodType.POST); queryRequest.setSysDomain("dysmsapi.aliyuncs.com"); queryRequest.setSysVersion("2017-05-25"); queryRequest.setSysAction("QuerySendDetails"); queryRequest.putQueryParameter("PhoneNumber", phone); queryRequest.putQueryParameter("SendDate", "20240101"); queryRequest.putQueryParameter("PageSize", "10"); queryRequest.putQueryParameter("CurrentPage", "1"); queryRequest.putQueryParameter("BizId", bizId);

查询结果里能看到每条短信的发送状态、运营商回执码、送达时间。真到排查环节,这条链路是救命用的。

3.4 把发送逻辑封装成独立模块

调用短信API只是第一步,真正的工程化是要把它封装成业务侧统一调用的模块。我的习惯是定义一个通用的发送接口:

  • 入参只有手机号、业务场景、模板占位符map
  • 模块内部根据场景映射签名和模板CODE
  • 发送前先做校验:手机号格式、变量完整性、是否在发送频率内
  • 发送后把记录写进日志表

好处是,如果后面从国内短信切到国际短信,或者接入第二个厂商做容灾,只需要替换模块内部实现,上层业务代码一行都不用改。

我在模拟项目X里就是照着这个思路做的:发送验证码、订单通知、营销短信全部走同一个模块,线上跑了半年,几乎没有因为短信代码问题返工。

3.5 业务侧的幂等与防刷逻辑

短信不是“只管发出去就行”,业务侧至少要加两道保险:

第一道是防刷。验证码接口如果裸奔,分分钟会被脚本刷爆,短信费用直接起飞。我在项目里通常这么处理:

  • 同一个手机号60秒内不允许重复发送验证码。
  • 同一个IP一小时内的验证码请求次数做上限。
  • 前端在点击“获取验证码”前,先过图形验证码或滑块验证。
  • 登录场景再结合历史行为做风控,异常设备直接拉黑。

第二道是缓存与过期。验证码生成后写入Redis并设置有效期,一般是5分钟。发送请求里带上同一个请求ID做幂等,防止用户连点时发出多条短信。

具体到代码里,验证码发送前的频率检查大概是:

String sendKey = "sms:verify:" + phone; Boolean first = redis.setIfAbsent(sendKey, "1", Duration.ofSeconds(60)); if (!Boolean.TRUE.equals(first)) { throw new BizException("请勿频繁获取验证码"); } // 生成4位或6位随机码 String code = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999)); // 存缓存,5分钟过期 redis.set("verify:code:" + phone, code, Duration.ofMinutes(5));

这里有个细节:setIfAbsent本身是原子操作,用同一个key做60秒去重,比先get再set稳妥得多。高并发下用非原子操作会出现“同时放行”的问题,短信就发重了。

4. 回执接收与状态追踪

4.1 为什么必须关注回执

短信发出去之后,运营商链路是异步的。短信可能被手机系统拦截、被运营商风控、因为签名问题被拒收。如果业务只关心SendSms的返回值,那用户“收不到验证码”的问题永远是黑盒。

回执机制就是让阿里云把短信的最终发送状态推送给你。拿到回执之后,业务才能做三件事:

  1. 对账:确认每条短信确实送达。
  2. 告警:如果一批短信大面积发送失败,立刻报警。
  3. 补偿:针对失败状态,做非敏感的替代触达。

4.2 控制台配置回执消息

在短信控制台的“回执消息”里,可以配置短信状态报告和上行消息的接收方式。两种方式:

  1. MNS消息队列:阿里云把回执消息推送到你自己的消息队列里,业务侧消费队列消息。适合发送量大的场景,消息不丢、可重试。
  2. HTTP推送:阿里云直接POST回执到你的回调接口。适合中小项目,配置简单。

HTTP推送需要注意几点:

  • 回调地址必须是公网可访问的HTTPS地址。
  • 阿里云会通过签名方式校验推送来源,推荐在代码里对推送内容做验签或来源IP校验。
  • 回调接口要做幂等处理,同一个回执消息可能会推送多次。

我实际用的是HTTP推送。上线前在配置里填好地址,然后找控制台的“测试回执”功能模拟一条回执,确认接口能通、数据能解析,再放量。

4.3 回执数据结构

不同触点的回执字段略有差异,状态报告的消息内容大体长这样:

{ "phone_number": "13800138000", "send_status": "1", "err_code": "DELIVRD", "template_code": "SMS_123456", "biz_id": "1234567890", "out_id": "verify_1620000000000", "send_time": "2024-01-01 10:00:00", "report_time": "2024-01-01 10:00:02" }
  • send_status为1表示成功,为2表示失败。
  • err_code是运营商返回的状态码,比如DELIVRD是送达,其它错误码需要查表。
  • biz_id和out_id用于和你的发送记录关联。

收到回执后,更新业务记录里的短信状态字段,前端就能展示“验证码已发送”或“验证码发送失败”。这个体验很多人忽略,但对用户来说感知非常直接。

上行消息,也就是用户回复短信的内容,结构类似,有content、send_time、dest_code等字段。如果业务需要做“回复短信退订”或者“回复关键词查询”,就需要处理上行消息。

4.4 失败重试与补偿策略

回执显示发送失败时,不要无脑重发。要先看错误码:

  • 如果是手机号问题(关机、停机、空号),重发没意义。
  • 如果是运营商限流,可以等一段时间再补发。
  • 如果是签名/模板问题,重发也会失败,需要修配置。

重试策略我建议这样:

  • 第一次失败,间隔30秒重试一次。
  • 第二次失败,间隔5分钟重试一次。
  • 仍失败则不再自动重试,转人工或换其它触达方式。

有一个容易漏掉的问题:短信验证码发送失败后,如果重试,必须重新生成验证码并更新缓存,否则用户拿着第一次的验证码,跟新发的对不上,体验更差。

5. 常见故障与排查速查表

5.1 验证码收不到怎么定位

用户说“收不到验证码”时,按下面顺序排查:

  1. 先在自己手机上看是否收到了。如果自己也收不到,直接看控制台的发送记录和回执。
  2. 查SendSms的返回。如果连Code=OK都没有,看错误码是什么。
  3. 查回执。回执里send_status是2,说明到了运营商侧但没送达,看err_code。
  4. 查手机拦截设置。被拦截的短信一般不会进运营商回执,需要让用户翻垃圾短信。
  5. 查运营商通道问题。某个运营商段长时间送达率下降,大概率是通道故障,这种只能等。

最实用的一招:把短信发送记录和回执记录都入库,支持按手机号和时间查。没有这个记录表,用户反馈问题时你只能干瞪眼。

5.2 高频错误码速查表

错误码含义处理方式
isv.MOBILE_NUMBER_ILLEGAL手机号非法校验号段,拦截测试号
isv.SMS_SIGNATURE_ILLEGAL签名不存在或未审核检查签名是否通过审核
isv.SMS_TEMPLATE_ILLEGAL模板不存在或未审核检查模板CODE是否正确
isv.INVALID_JSON_PARAMJSON格式错误检查TemplateParam是否合法JSON
isv.TEMPLATE_PARAMS_ILLEGAL模板变量不匹配核对变量名是否一致
isv.BUSINESS_LIMIT_CONTROL发送频率超限检查同一号码的发送频次
isv.AMOUNT_NOT_ENOUGH账户余额不足充值,设置余额预警
isp.FLOW_LIMIT系统限流降低发送频率,稍后重试
isp.RAM_PERMISSION_DENYRAM权限不足给子账号授权短信权限

遇到不认识的错误码,优先去官方错误码文档查,别自己猜。这里只列最常见的几个,很多朋友栽在isv.INVALID_JSON_PARAM上,查了半天参数名,结果是JSON串里多转义了一个花括号。

5.3 审核不通过案例集

边界案例最容易被驳回。我整理几个典型的:

  1. 签名带了“通知”“提醒”这类业务属性词:签名只放品牌名。
  2. 模板变量支持传链接:审核必驳,改成固定文案或者用短链服务包装后再发。
  3. 营销短信没有退订:必须带“回T退订”“回复TD退订”等清晰退订指引。
  4. 内容跟企业资质不符:模板内容要和签名主体业务强相关。

改完之后重新提交,一般审核周期是几个小时内。如果很急,可以提工单加急,但前提是前面几轮提交记录里没有反复提交相同内容。

5.4 频率限制的应对

阿里云对短信发送本身有频率限制策略,尤其在验证码场景。业务侧在高峰期如果单号码、单IP瞬时请求过多,会触发isv.BUSINESS_LIMIT_CONTROL。

应对方式是做业务层的队列化和削峰。高峰期把发送请求先放进队列,消费端按固定速率调用短信API。这样既避免触发频率限制,又不会因为瞬时流量把短信费用冲高。

但别走到另一个极端:验证码场景本身对时效有要求,队列堆积超过30秒用户就会不耐烦。所以队列消费速度要压测,不能拍脑袋定。

6. 运维、监控与成本控制

6.1 余额预警与监控

短信是按量计费,账户欠费时所有发送会直接失败。这个故障一旦发生,影响面是所有用户都收不到验证码,绝对算得上P0事故。我在多个项目里都遇到过类似问题,深有体会。

控制台自带按量预警,还可以在云监控里配置自定义告警:

  • 账户余额低于某阈值,立刻报警。
  • 单日短信费用超过预算阈值,报警。
  • 某个模板的发送失败率超过5%,报警。
  • 每分钟发送量突增,可能有刷单风险,报警。

告警渠道建议接电话和短信,邮件和钉钉在深夜打不醒人。

6.2 日志与链路追踪

短信日志至少要记录这些字段:

字段说明
request_id阿里云返回的请求ID
phone手机号
scene业务场景
template_code模板CODE
biz_id发送业务ID
out_id业务侧流水号
request_time请求时间
report_time回执时间
status最终状态

这个表就是短信业务的账本。运维排障、成本核算、业务审计都靠它。我用过比较省事的方式:业务日志直接打到日志服务里,再配几个统计报表,不用自建数据库。

6.3 降低短信成本的几条路

短信费在业务量上来之后是一笔实打实的开支。省钱不是砍功能,而是把每一分钱花在刀刃上:

  1. 验证码优先用短信,但非核心通知(如站内信优先级高的场景)可以切到App推送或微信公众号模板消息。
  2. 营销短信做好人群筛选,定向发送,别搞广撒网。
  3. 短信文案里的变量数量影响计费长度,超过字数上限会按多条计费。文案精简,既省钱又降低通道拒收概率。
  4. 对到达率不敏感的场景,可以选相对的“低价通道”,但要用回执数据做质量监控,便宜不代表不能选。

最后分享一个我踩过几次坑之后的体会:短信这个业务,看代码十分钟能跑通,但“线上稳”靠的是把前置审核、发送频率、回执追踪、余量告警这些细节全做到位。尤其是回执,很多人上线一个月都没看过一条回执数据,出了问题第一反应是“阿里云没发”,其实通道早就把失败状态推回来了,只是没人接。所以不管项目大小,建议把回执配置这一步放到和发送代码同等重要的位置。真等线上出了“用户收不到验证码”的投诉再补,代价至少是好几倍。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询