1. 消息推送这件事,为什么值得单独拎出来讲
做微信小程序开发这些年,被问得最多的问题里,“消息推送”绝对排得进前三。不管是电商项目里订单状态变更要通知用户,还是校园跑腿平台里接单提醒要触达骑手,又或者是内容社区里评论回复要唤醒用户,消息推送都是绕不开的一环。但很多人第一次接触这块的时候,往往会被“模板消息”和“订阅消息”这两个概念搞混,更别说搞清楚它们之间的演进逻辑和适用边界了。
我最早做小程序推送是在模板消息还大行其道的阶段,那时候只要用户在小程序里完成一次支付行为,开发者就能在七天内往用户微信里塞一条通知,触达率相当可观。后来规则调整,模板消息逐步下线,订阅消息登上舞台,整个推送逻辑从“一次授权长期可用”变成了“一次授权一次推送”,这对产品设计和开发实现都提出了完全不同的要求。如果你现在还在用老思路做新项目,大概率会踩坑。
这篇文章想做的事情很明确:把微信小程序消息推送从模板消息到订阅消息的演进脉络讲清楚,把订阅消息的完整实操流程拆开揉碎,包括授权时机怎么选、模板怎么配、后端怎么调、常见报错怎么排查。不管你是刚接触小程序开发的新手,还是从模板消息时代迁移过来的老手,都能从中找到可以直接抄作业的内容。文章会涉及不少代码和配置细节,建议边看边对着开发者工具操作。
2. 从模板消息到订阅消息,到底变了什么
2.1 模板消息时代的核心逻辑与历史局限
模板消息的本质是“支付即授权”。用户在小程序内完成一次支付行为后,开发者获得了一次向该用户发送模板消息的权限,这个权限在支付后的七天内有效,最多可以发送三条。这个机制在当时看很巧妙,因为支付本身就是一个强意图行为,用户对后续通知的接受度相对较高。
但问题也很明显。第一,触发场景被死死绑定在支付上,非支付场景的通知需求完全无法满足,比如用户预约了服务但还没付款,你就没办法提醒他。第二,模板消息的模板库是微信官方统一维护的,开发者只能从现有模板里挑选,不能自定义内容结构,灵活性很差。第三,也是最致命的一点,部分开发者滥用这个能力,在用户支付后疯狂推送营销内容,导致用户体验急剧下降,投诉量飙升。
我印象很深的一个案例是,有个做知识付费的小程序,用户买完课程后连续三天收到营销推送,最后被用户举报到封禁推送能力。这种滥用行为直接加速了模板消息的退场。
2.2 订阅消息的授权模型与关键约束
订阅消息把授权粒度收得更细了。它的核心模型是“用户主动订阅,开发者按次推送”。用户在某个操作节点上主动点击订阅按钮,同意接收某一类消息,开发者就获得了一次推送额度。用户订阅一次,你只能推一次,推完额度就消耗掉了。
这个模型有几个关键约束需要刻在脑子里。第一,订阅行为必须由用户主动触发,不能由开发者静默调用,也就是说你不能在用户不知情的情况下帮他勾选订阅。第二,一次性订阅和长期订阅是两回事,长期订阅只对特定行业开放,比如政务、医疗、交通等公共服务领域,普通开发者只能用一次性订阅。第三,订阅消息的模板虽然比模板消息灵活一些,但依然需要从公共模板库中选择,或者申请自定义模板,审核通过后才能使用。
注意:一次性订阅的额度消耗是不可逆的,用户点一次订阅你只能推一条。如果推送失败,额度同样会被消耗,不会返还。所以推送前的参数校验一定要做足。
2.3 两种消息形态的对比与选型建议
| 对比维度 | 模板消息 | 订阅消息 |
|---|---|---|
| 授权方式 | 支付后自动获得 | 用户主动点击订阅 |
| 推送次数 | 七天内最多三条 | 订阅一次推一次 |
| 触发场景 | 仅限支付后 | 任意用户操作节点 |
| 模板灵活性 | 低,官方固定模板 | 中,可选公共模板或申请自定义 |
| 行业限制 | 无特殊限制 | 长期订阅仅限特定行业 |
| 当前状态 | 已逐步下线 | 官方主推方案 |
选型建议很直接:新项目一律用订阅消息,不要再考虑模板消息。老项目如果还在用模板消息,尽快迁移,因为接口随时可能完全关闭。迁移的核心工作是把原来依赖支付触发的推送逻辑,改成在关键操作节点引导用户订阅。
3. 订阅消息实操全流程拆解
3.1 模板申请与参数配置的坑
第一步是去微信公众平台的订阅消息模块申请模板。这里有个细节很多人不知道:公共模板库里的模板并不是所有都能用的,有些模板带有行业限制,你的小程序类目如果不匹配,根本搜不到。所以申请之前先确认自己的小程序类目,然后在模板库里按类目筛选。
申请模板时需要填写“关键词”,这些关键词决定了模板最终呈现给用户的内容结构。比如一个订单发货通知模板,关键词可能是“订单编号”“商品名称”“发货时间”“快递单号”。关键词的顺序和数量都有讲究,顺序决定了用户看到的消息排版,数量一般不超过五个,太多会让消息显得冗长。
模板申请通过后,你会拿到一个模板ID,这个ID是后端调用推送接口时的必填参数。同时每个关键词会对应一个参数名,比如thing1、time2、character_string3,这些参数名在后续调用时用来填充具体内容。
实操心得:模板关键词的类型是有严格校验的。
thing类型最多20个字符,time类型必须是特定格式,character_string类型有字符集限制。填充参数前一定要对照官方文档确认类型,否则会直接报错。
3.2 前端授权时机的选择策略
订阅消息的授权弹窗不能随便弹,弹得太频繁用户反感,弹得太少又攒不够推送额度。我的经验是,把订阅请求嵌入到用户完成某个关键动作之后的自然节点上。
比如电商小程序,用户点击“提交订单”之后、跳转到支付页面之前,这个节点弹订阅请求最合适。因为用户此时对订单状态的关注度最高,订阅意愿最强。再比如预约类小程序,用户选完时间段点击“确认预约”时弹订阅,逻辑上也顺理成章。
代码层面,调用wx.requestSubscribeMessage方法,传入模板ID数组,用户勾选同意后,回调里会返回每个模板的订阅状态。这里要注意,用户可能只勾选部分模板,所以回调结果要逐个判断,不能假设全部成功。
wx.requestSubscribeMessage({ tmplIds: ['模板ID1', '模板ID2'], success(res) { if (res['模板ID1'] === 'accept') { // 用户同意了模板1的订阅 } if (res['模板ID2'] === 'reject') { // 用户拒绝了模板2的订阅 } }, fail(err) { console.error('订阅请求失败', err) } })还有一个容易被忽略的点:wx.requestSubscribeMessage必须由用户点击行为触发,不能在页面加载时自动调用,否则会直接失败。我见过有开发者把它放在onLoad里,结果一直报“requestSubscribeMessage:fail can only be invoked by user TAP gesture”,排查半天才发现是调用时机不对。
3.3 后端推送接口的调用细节
前端拿到用户的订阅授权后,后端才能调用推送接口。这里的关键是,前端需要把用户的订阅状态同步给后端,后端记录哪些用户对哪些模板有可用额度。
推送接口的核心参数包括:用户的openid、模板ID、跳转页面路径、模板数据。模板数据是一个对象,键名对应模板关键词的参数名,值就是具体内容。
{ "touser": "用户openid", "template_id": "模板ID", "page": "pages/order/detail?id=123", "data": { "thing1": { "value": "订单已发货" }, "character_string2": { "value": "SF1234567890" }, "time3": { "value": "2024-01-15 14:30" } } }调用推送接口需要access_token,这个token的有效期是两小时,需要定时刷新。建议用中控服务统一管理token,避免多个业务模块各自刷新导致token冲突。
注意:推送接口的调用频率有限制,单个小程序每天调用上限与用户量相关。如果推送量很大,建议做队列缓冲,避免瞬时并发过高被限流。
4. 推送失败排查与常见问题实录
4.1 授权相关报错的排查思路
最常见的报错是“用户未订阅该模板”或“订阅额度已用完”。前者说明用户根本没有授权过这个模板,后者说明授权过但额度已经消耗完了。排查的时候先确认前端是否成功调用了wx.requestSubscribeMessage,再确认后端记录的额度是否准确。
还有一种情况是用户授权了,但后端推送时用的openid和授权时的openid不一致。这在多小程序或多公众号场景下容易出现,因为同一个用户在不同应用下的openid是不同的。解决办法是统一用unionid做用户标识,推送时再换取对应小程序的openid。
4.2 参数格式错误的快速定位
参数格式错误是另一个高频问题。比如thing类型的参数值超过了20个字符,或者time类型的值不是yyyy-MM-dd HH:mm:ss格式,都会导致推送失败。报错信息通常会指明是哪个参数出了问题,但有时候报错比较模糊,只提示“参数不合法”。
我的做法是在后端封装一个参数校验层,在调用推送接口之前先按模板关键词的类型做一轮校验。比如thing类型截断到20字符,time类型统一格式化,character_string类型过滤掉不支持的字符。这样能把大部分参数错误拦截在推送之前。
| 报错信息 | 可能原因 | 解决方向 |
|---|---|---|
| 用户未订阅该模板 | 用户未授权或授权已过期 | 检查前端授权流程,重新引导订阅 |
| 订阅额度已用完 | 推送次数超过授权次数 | 记录额度消耗,及时补充订阅 |
| 参数不合法 | 参数类型或长度不符合要求 | 按模板关键词类型做校验和格式化 |
| access_token无效 | token过期或刷新失败 | 检查token管理逻辑,确保定时刷新 |
| 推送频率超限 | 瞬时并发过高 | 增加队列缓冲,控制推送速率 |
4.3 用户收不到消息的几种可能
有时候推送接口返回成功,但用户就是收不到消息。这种情况通常有几个原因。第一,用户关闭了微信的通知权限,消息被系统拦截了。第二,用户把小程序删除了或者长时间未使用,微信会降低推送优先级。第三,消息内容被微信判定为营销信息,做了折叠处理。
排查这类问题时,先确认接口返回的errcode是否为0,如果是0说明微信侧已经接收了推送请求。然后让用户检查微信的通知设置,确认没有关闭小程序的推送权限。如果都正常,那可能是内容层面的问题,尝试调整消息文案,避免使用营销敏感词。
5. 进阶场景与性能优化建议
5.1 批量推送的队列化处理
当用户量上来之后,推送需求往往不是单条的,而是批量的。比如每天早上给所有有预约的用户推送提醒,或者大促期间给所有下单用户推送物流更新。这种场景下,如果直接循环调用推送接口,很容易触发频率限制。
我的做法是引入消息队列,把推送任务先写入队列,然后由消费者按固定速率消费。队列的好处是可以削峰填谷,避免瞬时并发过高。同时可以在消费者端做失败重试,提高推送成功率。队列的实现可以用Redis的List结构,也可以用专门的消息中间件,看项目规模决定。
5.2 推送内容的个性化与模板复用
订阅消息的模板是固定的,但内容可以个性化。比如同一个订单发货模板,可以根据用户购买的商品类型填充不同的文案。这里的关键是后端在组装模板数据时,根据业务逻辑动态生成内容。
模板复用方面,尽量用少量模板覆盖多个场景。比如一个“状态变更通知”模板,可以同时用于订单状态变更、预约状态变更、审核状态变更等多个场景,只需要在文案上做区分。这样做的目的是减少模板申请的工作量,也方便统一管理。
5.3 数据埋点与推送效果追踪
推送发出去了,效果怎么样?这就需要埋点追踪。关键指标包括:推送成功率、用户点击率、订阅转化率。推送成功率反映技术层面的稳定性,用户点击率反映内容层面的吸引力,订阅转化率反映授权引导的有效性。
埋点的实现方式是在推送时记录一条日志,包含用户ID、模板ID、推送时间、推送结果。然后在用户点击消息进入小程序时,再记录一条点击日志。通过对比推送日志和点击日志,就能算出点击率。这些数据反过来可以指导推送策略的优化,比如调整推送时间、优化文案、改进授权引导时机。
6. 一些踩坑之后的个人体会
做消息推送这几年,最大的体会是:技术实现只是冰山一角,真正决定推送效果的是对用户心理和产品节奏的把控。订阅消息的“一次授权一次推送”模型,本质上是在逼开发者想清楚——你到底要在什么时刻、用什么理由、让用户心甘情愿地订阅你。
我见过太多项目把订阅弹窗做得像牛皮癣一样,用户点哪里都弹,结果订阅率极低,推送额度永远不够用。也见过一些项目把订阅引导做得极其自然,用户在完成某个动作后顺手就点了同意,推送触达率一直很健康。差别不在于技术,而在于对场景的理解。
还有一个很实际的建议:推送文案要像人话。不要用“您的订单状态已更新”这种冷冰冰的模板腔,改成“你的快递已经出发啦,预计明天下午到”这种有温度的表达。用户感受到的是通知,而不是骚扰,点击率自然就上去了。
最后分享一个小技巧:如果某个模板的推送点击率持续偏低,不要急着放弃,先试试调整推送时间。同样的内容,早上八点和晚上八点推送,效果可能差好几倍。找到用户最可能看手机的时间窗口,比优化文案本身更有效。