接入教程2026-11-04· 12 分钟阅读

变量短信与长短信怎么发:模板规范、拆分计费与对账口径

变量短信与长短信的工程做法:变量占位符六个细节(位置与报备一致/填充前校验/空值兜底/JSON转义/批量错位防护/链接不动态)、长短信拆分如何改变计费条数、回执与请求一对多导致对账键必须用request_id+分片序号、批量按分片并发与重试幂等,以及变量与长短信叠加时的五步排查顺序与十一项上线检查单。

诺
诺正通研究院

变量短信和长短信是两个不同的坑:前者解决"同一条模板要给不同的人不同内容",后者解决"一条内容太长会被拆开"。很多团队上线后才发现后者的坑——你以为自己发了 1 千条短信,用户收到了 2 千条,账单也是 2 千条。

这篇讲清楚两类短信的模板规范、拆分计费,以及对账时最容易出错的地方。

一、两个概念的边界

变量短信长短信
解决什么同模板、不同内容单条装不下的内容
表现模板里有占位符,逐条替换内容超过单条上限,被拆成多条
计费按条按拆分后的条数
报备报模板 + 变量位置报模板(内容需与实际发送一致)
常见坑变量为空、JSON 转义、批量时变量错位对账时条数对不上

两者可以叠加:一条带变量的长短信,拆成多条后每条的变量还不一样——这是最难调通的情况。

二、变量短信:占位符的六个细节

变量短信的本质是"模板 + 变量"。模板报备时就要把变量位置说清楚,发送时按同样的结构填值。

1. 变量位置必须和报备时一致

报备模板是 您的验证码是 {code},发送时就只能把数字填在那个位置。把变量挪到句子中间,属于"内容与报备不一致",会被驳回或拦截。改模板结构 = 重新报备。

2. 变量值要限制长度和字符集

验证码是 4-6 位数字,订单号是固定格式。变量长度超出预期时,轻则内容被截断,重则整条被拦。工程做法是在填充前校验:

import re

PATTERNS = { "code": re.compile(r"^d{4,8}$"), "order": re.compile(r"^[A-Za-z0-9]{6,32}$"), "name": re.compile(r"^[一-龥A-Za-z·]{2,20}$"), }

def fill(template: str, variables: dict) -> str: for key, pattern in PATTERNS.items(): v = variables.get(key) if v is None: raise ValueError(f"变量 {key} 缺失") if not pattern.match(str(v)): raise ValueError(f"变量 {key} 格式不合法: {v!r}") return template.format(**variables)

先校验再填充,比事后从错误短信里反查便宜得多。

3. 变量为空要有兜底策略

用户没填姓名、订单还没生成,这些是常态。策略要明确:丢弃这条、用默认文案、还是降级成不含变量的模板——选一种并写进代码。默认用 str.format 的话,空值会渲染成 None,用户会看到"您的验证码是 None"。

4. JSON 里的变量要转义

短信内容作为 JSON 字段传递时,引号和反斜杠必须转义,否则请求体直接解析失败。变量里如果混入 " 或换行,会把整条短信截断——验证一次带特殊字符的变量,成本很低。

5. 批量发送时变量与手机号必须成对

批量接口常见两种形态:一次提交一个数组,或一次提交一个模板 + 变量数组。用数组时,顺序错位是最典型的批量事故——用户 A 收到用户 B 的验证码。防御手段:

# 变量数组与手机号数组长度必须一致,且发送前做一次抽样校验

assert len(phones) == len(vars_list)

抽样:用前 3 条做占位符渲染后的长度/格式自检

for p, v in list(zip(phones, vars_list))[:3]: filled = fill(template, v) assert "{{" not in filled and "}}" not in filled, "变量未全部替换"

更稳的做法是传对象数组([{phone, vars}, ...]),让手机号和变量在结构上绑死,从源头消除错位。

6. 变量里的链接要单独考虑

如果模板里带链接,链接也要报备,且不支持动态拼接——变量里塞不同的链接等于每次换链接,等于没报备。带链接的场景见短信带链接怎么发与短链服务设计。

三、长短信:拆分与计费

1. 拆分会改变计费条数

内容超过单条上限时,会被拆成多条下发、按条计费。所以:

你发送:1,000 条长短信(每条内容都超限)

用户收到:约 2,000 条(实际以运营商拆分规则为准) 账单:按 2,000 条计

先确认你的模板是不是长短信——如果模板里只有一句"您的验证码是 123456",它不是长短信;如果模板里塞了活动说明、地址、退换政策,那它很可能就是。上线前把模板内容实际渲染一遍,量一下长度,比事后看账单便宜。

2. 拆分会打乱回执与请求的对应关系

这是最实际的坑。通道回执是"按实际下发的短信条"返回的,而你的请求是"按用户条":

请求:user_001(长短信,拆成 2 条)

回执:可能收到 2 条回执,分别对应这条短信的第 1 段、第 2 段

如果对账逻辑用"手机号 + 验证码"做键,长短信场景下会出现一对多的匹配,轻则重复计费统计,重则把失败当成成功。

正确做法是:对账键 = 请求 ID + 分片序号,而不是业务字段。

-- 请求与回执对账(按分片序号匹配)

SELECT r.request_id, r.phone, COUNT(h.msg_id) AS delivered_parts FROM sms_send_log r LEFT JOIN sms_delivery_log h ON h.request_id = r.request_id WHERE r.created_at >= :since GROUP BY r.request_id, r.phone HAVING delivered_parts <> r.expected_parts; -- 分片数不一致 = 待排查

3. 批量发送要按分片并发,而不是按用户并发

长短信场景下,一个用户对应多条实际下发,速率压力是按分片算的。并发与限流的做法见批量实名核验怎么并发——那套分片 + 退避 + 幂等键的做法在这里完全适用。

4. 重试必须带幂等键

长短信发送失败后重试,如果接口没有幂等能力,用户可能收到两条重复的长短信(还多付一次钱)。重试前先确认这个接口是否支持幂等键;不支持的话,自己在发送侧记录 request_id 并在重试前查询状态。

四、变量 + 长短信叠加时的排查顺序

遇到"用户说收到的内容和预期不一样",按这个顺序查,不要跳步:

1. 渲染后的实际内容长什么样?(拿 request_id 找回原始内容快照)
  • 该内容的长度是多少?是否触发拆分?
  • 回执条数与 expected_parts 是否一致?
  • 变量值与手机号是否错位?(批量场景优先查这个)
  • 模板与报备版本是否一致?(模板改过但没重新报备)
  • 第 1 步最容易被跳过,但它是后面所有判断的基准:没有原始内容快照,排查就只能靠猜。发送日志里至少要留 request_id + 手机号 + 渲染后内容 + 模板版本。

    五、上线检查单

    □ 变量位置与报备模板一致,改结构即重新报备
    

    □ 变量填充前做格式校验(长度、字符集) □ 变量为空有明确兜底策略(不用 str.format 的默认空值) □ 变量值做过 JSON 转义,含特殊字符的样例已验证 □ 批量用对象数组传参,避免手机号与变量错位 □ 发送前抽样渲染自检,确认无未替换占位符 □ 模板实际渲染长度已量过,确认是否属长短信 □ 对账键 = request_id + 分片序号,不用业务字段 □ 批量按分片并发,限流按实际下发条数计 □ 重试带幂等键(或发送侧自查 request_id 状态) □ 发送日志保留 request_id、渲染后内容、模板版本

    相关阅读

  • 短信验证码接口:三网合一、可带链接、变量短信、实时状态回执
  • 消息通知接口接入指南:签名规则、模板报备与发送频率控制
  • 短信签名模板报备流程:签名与模板规范、常见驳回原因
  • 批量实名核验怎么并发:分片并发、限流与幂等键
  • 短信验证码被刷怎么防:发送侧的安全防护
  • 短信通道按条计费,签名与模板需报备;具体计费口径(含长短信拆分规则)与配额请与商务确认,新用户有免费额度可用于联调。
    #变量短信#长短信#短信模板#对账#批量发送

    看完想立刻接入?

    100 次免费调用,1 个工作日开通

    限时新用户 100 次免费

    1 个工作日开通 · 7×24 技术支持