ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

双11预售邮件别靠倒计时堆频次:先写清发送状态机

双11预售邮件别靠倒计时堆频次:先写清发送状态机

离双11还有一段时间,预售邮件可以先设计,不能提前写成活动已经结束的复盘。一封“最后机会”发错时间,比少发一封邮件更容易损害信任。

我会先写发送状态,再写标题:用户是否订阅,是否已经购买,是否退订,当前阶段是否到达。文案再漂亮,也不能绕过这些条件。

从日期列表改成资格判断

按日历排“预热、提醒、结束”只是第一层。买过的人不应该继续收到催购买,退订的人不能因为导入了旧名单又进入流程。延迟任务执行时也要重新读取资格,不能只相信入队时的快照。

Stripe webhook文档说明,事件交付不保证生成顺序,重复交付也可能发生。因此,订阅或支付事件不能简单按到达先后覆盖状态。具体支付对象需要按业务规则读取权威状态;下面只演示已经确认状态之后的邮件资格判断。

资格闸门放在发送之前,而不是只放在排期界面。图里被抑制的分支应留记录,方便解释某封邮件为什么没有发出。

先用本地样本验证闸门

const assert = require('node:assert/strict'); function eligible(user, campaign, now) { if (!Number.isFinite(now) || !Number.isFinite(campaign.start) || !Number.isFinite(campaign.end) || campaign.end <= campaign.start) throw new TypeError('invalid campaign window'); return user.subscribed === true && user.unsubscribed !== true && user.purchased !== true && now >= campaign.start && now < campaign.end; } const campaign = { start: 100, end: 200 }; const user = { subscribed: true, unsubscribed: false, purchased: false }; assert.equal(eligible(user, campaign, 100), true); assert.equal(eligible(user, campaign, 200), false); assert.equal(eligible({ ...user, unsubscribed: true }, campaign, 150), false); assert.equal(eligible({ ...user, purchased: true }, campaign, 150), false); console.log('mail-gate: 4 assertions passed');

保存为mail-gate.cjs,运行node mail-gate.cjs。时间100和200只是模拟刻度,真实活动使用带时区的绝对时间。代码不调用邮件平台,不证明送达率或退订同步可靠。

失败重试不能变成重复轰炸

每个用户、活动与阶段应有稳定发送标识。发送成功后保存供应商消息ID;超时则先查询已知状态或进入人工核查,避免仅因为客户端没收到响应就再发一遍。

频率上限也要跨活动生效。一个人可能同时处于欢迎、产品更新、促销三个序列,单条序列看起来克制,合起来却每天数封。前端应提供收件人预览和抑制原因,不只显示“预计发送1000人”。

倒计时只用于真实的截止条件。延期时同步活动页、邮件落地页和结算逻辑,避免邮件显示已结束、网站仍按原价收款。这个设计没有实测营销效果;更可靠的起点,是先保证每封邮件对当前收件人都有发送理由。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进