01 · Before and afterThe pattern
One idempotent step per side effect, keyed by a stable non-personal ID, with a fresh state check inside.
// before: one step for the whole batch; a retry re-sends to everyone
await io.runTask("notify-all", () => Promise.all(recipients.map(send)));
// after: one step per recipient; finished steps are skipped on retry
for (const r of recipients) {
await io.runTask(`notify-${r.id}`, async () => {
if (!(await stillEligible(r.id, envelopeId))) return; // re-check at run time
await send(r);
});
}02 · Run it on your appThe check
If your email job crashes on the third of five recipients and retries, do the first two get the email twice?
If the honest answer is no or not sure, this is worth a look before it reaches production.