
活动任务显示超时,用户又点一次发送。第二次返回成功,第一封却也已经在路上。对一个人维护的产品,自动化最棘手的故障往往不是彻底失败,而是结果未知。
我会先记录任务身份和状态,再接发送工具。界面要让用户知道正在执行、已完成、失败可重试和需要核查是不同状态,不能把所有非成功都做成同一个重试按钮。
先确认操作是否已经发生
Stripe幂等请求说明展示了通过稳定键识别同一操作重试的做法,并明确不同API代际的语义差别。应用可以借鉴这个思路,但不能因此声称整个营销链路实现了恰好一次执行。
同一活动、用户和阶段应有稳定任务ID。内容发生变化时不能悄悄复用同一个ID;超时后先查任务及供应商状态,必要时人工核对,再决定是否重试。

图里未知结果走核查分支,不直接回到发送。这个分支需要留在UI里,即使它不如一个绿色对勾好看。
内存样例只说明重复登记的规则
const assert = require('node:assert/strict');
function registry() {
const tasks = new Map();
return function register(id, payload) {
if (typeof id !== 'string' || !id || typeof payload?.template !== 'string' ||
!payload.template || Object.keys(payload).length !== 1)
throw new TypeError('invalid task');
const signature = payload.template;
if (tasks.has(id)) {
const old = tasks.get(id);
if (old.signature !== signature) throw new Error('same id, different payload');
return { duplicate: true, state: old.state };
}
tasks.set(id, { signature, state: 'queued' });
return { duplicate: false, state: 'queued' };
};
}
const register = registry();
assert.equal(register('campaign:user:stage', { template: 'a' }).duplicate, false);
assert.equal(register('campaign:user:stage', { template: 'a' }).duplicate, true);
assert.throws(() => register('campaign:user:stage', { template: 'b' }));
console.log('task-registry: 3 assertions passed');
保存为task-registry.cjs,运行node task-registry.cjs。签名只覆盖这个固定样本中唯一的template字段。生产系统应覆盖所有影响操作的参数,并明确定义规范签名。Map不持久化、不跨进程原子,也不和外部发送构成事务,不能直接用作生产防重系统。
错误信息要帮助下一步判断
“网络错误”不足以指导重试。任务详情可以展示最后确认阶段、外部消息ID是否存在,以及还缺少什么证据。不要把重试次数当成成功率,也不要让模型根据一段超时日志推测邮件一定没发出。
这篇没有发送邮件、连接营销平台或验证分布式幂等,只测试本地登记规则。自动化的可靠性来自把不确定状态管理好,而不是给流程起一个“自动获客”的名字。




