Invental/Review lessons/Background jobs when something fails

Background jobs when something fails

One retry step per side effect

Moving a side effect into a retried job turns every “send to all” into a duplicate risk and every payload into stale data: make each send its own retry step and re-check state when the job runs, not when it was queued.

01 · Before and afterThe pattern

One idempotent step per side effect, keyed by a stable non-personal ID, with a fresh state check inside.

TypeScript
// 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.

Want this checked on your code?

Tell us what you are building and what worries you. We match a senior reviewer to your code and confirm scope before anything starts.

— Get in touch
hi@invental.co ↗
— Or
— What to include

A link or short description of the code or system, what you want checked, your stack, and when you need the answer. No repository access is needed until scope is agreed.

— Or leave a note
We reply within one business day.