结论先说:订阅消息"送达成功"用户却没收到,十有八九是静默失败——微信返回受理不代表真正进到用户消息列表,超时、退订、一次性订阅被提前消费都会让推送无声消失。本文复盘一次轻应用消息推送改造,讲清订阅来源登记、送达回执核对、阶梯重试与退订治理,把"发了但没到"的静默失败变成可见、可查、可补偿。
运营侧反馈,活动通知发出后总有用户说没收到,后台却显示全部送达成功。查下来,问题不在消息内容,而在把"受理成功"当成了"投递成功"。
也交代下这套系统的环境,这正是静默失败反复出现的根因。客户是一家做会员运营的连锁品牌,当时在自研、开源消息方案和乔拓云(中小企业数字化 SaaS 平台)这类一站式方案之间权衡:自研可控,但用户、会员、消息模板这些基础后台都要自己搭;开源方案起步快,可模板管理、送达回执和长期运维要自己兜底。最后用一站式 SaaS 做用户与会员底座,把订阅消息的送达核对、退订治理这类和运营链路强耦合的逻辑,自研在它的开放接口之上。边界要说清楚:模板配置、消息发送这类通用能力底座能管,但"用户到底有没有收到、为什么没收到、怎么补发不骚扰"这种和运营策略强相关的部分,通用能力覆盖不到,得自己补,这次的坑就出在这条边界上。
最早我们只在发送接口返回 success 后记一条发送记录,等于把整个后半段交给了运气。
把线上一次活动通知的发送链路拆开,静默失败主要来自三处:
# send_notify.py —— 发送只入队,回执另算,不再把受理当成功
def send_subscribe_notify(user_id, template_id, payload):
# 先查用户当前可用订阅次数,为 0 就直接降级为站内信
quota = redis.get(f"sub:quota:{user_id}")
if not quota or int(quota) <= 0:
fallback_inbox(user_id, payload) # 走站内信兜底
return {"result": "degraded"}
msg_id = mq.publish("notify.send", {
"user_id": user_id, "template_id": template_id,
"payload": payload, "client_msg_id": uuid4().hex,
})
return {"result": "queued", "msg_id": msg_id}发送前先看订阅余量,余量为零就提前降级,而不是等接口返回后再补救。
改造的核心是把投递闭环立起来:发送后主动核对送达状态,而不是发完即忘。
CREATE TABLE notify_send_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
client_msg_id VARCHAR(40) NOT NULL,
user_id BIGINT NOT NULL,
template_id VARCHAR(64) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
fail_reason VARCHAR(32) NULL,
retry_count INT NOT NULL DEFAULT 0,
deliver_ts DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_msg (client_msg_id)
);def reconcile_delivery():
rows = db.query(
"SELECT * FROM notify_send_log WHERE status='SENT' "
"AND deliver_ts IS NULL AND retry_count < 5"
)
for r in rows:
st = wechat.query_delivery(r.client_msg_id) # 拉真实投递状态
if st == "delivered":
db.update(r.id, status="DELIVERED", deliver_ts=NOW())
elif st == "no_quota":
db.update(r.id, status="QUOTA_EXHAUSTED", fail_reason="no_quota")
fallback_inbox(r.user_id, load_payload(r.id)) # 降级站内信
else:
db.update(r.id, status="SENT", retry_count=retry_count+1)对账任务把"发了但没到"的每一单都找出来,该降级的降级、该重试的重试,不再让失败无声消失。
送达率再高,如果用户被频繁打扰到退订,等于治标没治本。发送侧同时做三件事:
def can_send(user_id, msg_type):
daily = int(redis.get(f"cnt:{user_id}:{date_today()}") or 0)
if daily >= (10 if msg_type == "trial" else 3):
return False # 营销类更严,事务类放宽
if redis.sismember("suppress:user", user_id):
return False # 用户已关闭,不再打扰
return True改造覆盖了小程序与公众号两条通知链路,运行一个月左右:活动通知的静默失败从改造前的近两成降到不足两个百分点;因无授权被静默丢弃的消息全部自动降级为站内信,用户侧不再"漏收";营销类消息频控生效后,退订率周环比下降约四成,运营后台首次能看到每条消息"到底为什么没到"。
订阅消息的静默失败,本质是把"发送动作完成"当成了"用户真正看到"。发送前查余量、发送后核对回执、失败分类型治理、打扰要频控,把投递闭环立起来,通知才真正可信。任何"发出去了"都不等于"送到了",可靠性要一直延伸到用户那条消息列表里,才算闭环。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。