Skip to main content
The recovery engine page covered what gets decided. This page covers how the worker executes it — the scan, the retry job, and the re-decision loop.

Two moving parts

The scanner is a cross-tenant sweep of due schedules — state = scheduled AND nextAttemptAt ≤ now — across both schemas. It only enqueues; the retry itself is a separate job so a slow gateway call never holds the scan open.

processDue — execute one retry, then re-decide

The crucial property: the strategy is consulted on every attempt, not just the first. A failure that started as insufficient_funds and waited for payday might, on the payday retry, come back as a do_not_honor — and the engine re-decides from scratch with the new code, the new attempt count, and the schedule’s stored paydayAware/offsets. Recovery is adaptive, not a fixed script.

The in_flight guard prevents double-charges

Setting state = in_flight before charging, and the state == scheduled guard at the top, mean two workers that somehow grab the same schedule can’t both charge it. The first claims it (scheduled → in_flight); the second sees in_flight and returns. Combined with the BullMQ jobId dedup, a retry executes at most once.

Manual recovery

Merchants don’t only wait for the timer. The dashboard’s Recovery Command Center can force a retry now: Same processDue path — the manual button and the timer converge on one code path, so they can never diverge in behaviour.

What the merchant sees

The whole thing renders as a live operations board:
  • Three columnsat riskrecoveringrecovered / lost — each card a failing subscription with its failure code, attempt N/5, chosen rail, and next action.
  • A per-card timeline — retries plotted on a calendar, the rail-switch relay drawn out, payday waits marked ”⏳ waiting for the 28th.”
  • The funnelbyFailureCode showing exactly what’s killing this merchant’s revenue, so they can fix the upstream cause.
All of it is fed by the RecoverySummary and the events table — no separate analytics pipeline, just reads over the operational data. Next: money in →.