Part 5 of 7 · Dunning recovery series ~7 min read

How access gets paused and restored

Pausing access is the one thing this system does that the customer actually feels, so it has to be exactly right: never early, never silent, and instantly reversible. This post is about the gatekeeper — the small entitlement check your app calls on every request — how it flips a subscription to paused only after the last retry has genuinely failed, and how a successful retry or an updated card restores access in the same second.

Key takeaways

  • Access is gated by one small entitlement check your app calls — dunr-gatekeeper behind a Function URL.
  • Access stays granted all through the retry window; it pauses only after the final retry has genuinely failed.
  • Pausing is reversible. A successful retry or an updated card restores access in the same second — no overnight batch.
  • The gatekeeper reads state from dunr-dunning and answers in single-digit milliseconds, cached at the edge of your app.
  • It never cancels. The strongest action it takes on its own is a pause; cancelling is always a human decision.

One question on every request

Everything in the previous four parts has been invisible to the customer. Pausing access is the one thing they actually feel — so it has to be exactly right: never early, never silent, and instantly reversible. All of that hangs on a single question your app already asks in some form on every request: is this customer allowed in?

In this system that question is answered by dunr-gatekeeper, a small Lambda behind a Function URL. Your app calls it — on login, on a protected API call, on a page that costs you money to serve — with a subscription id, and gets back one of two answers: granted or paused. That’s the entire contract. The gatekeeper reads the current state from the dunr-dunning table and translates it: active, retrying, and recovered all mean granted; only paused means paused. Because it’s a single keyed read, it answers in single-digit milliseconds, and your app caches the answer for a minute or two at its own edge so the check adds no noticeable latency.

Access survives the whole retry window

A subscriber whose card just blipped should not be locked out while you’re still trying to charge it. That would punish exactly the customers most likely to recover — the ones whose card expired or hit a temporary hold — and it would generate angry support tickets for people who are about to pay you anyway. So all through the retrying state, across all four attempts and the roughly ten days they span, the gatekeeper answers granted. The customer keeps full access while the retries and the dunning emails do their work quietly in the background.

Access flips to paused at exactly one moment: when the final retry fails. At that point dunr-retrier updates the row to paused and the next entitlement check returns paused. The customer now sees the update-card screen instead of the product — not an error, not a dead end, but a clear “your payment didn’t go through; update your card to restore access” with the same one-tap link from the emails. Pausing is deliberately the strongest thing the system does on its own, and it’s a reversible state, not a deletion.

The state machine for access: granted through retries, paused after the final failure, restored on recovery A state diagram with five states drawn as boxes connected by labelled arrows. On the left, an "Active" state, where access is granted. An arrow labelled "charge fails" leads right to a "Retrying" state, where access is still granted and retries plus dunning emails run. From Retrying, two arrows. One labelled "a retry succeeds, or card updated and charge clears" leads up to a "Recovered" state, where access is restored instantly and remaining retries are cancelled; a dotted arrow loops Recovered back to Active for the next billing cycle. The other arrow, labelled "final retry fails", leads down to a "Paused" state, where access is paused and the customer sees the update-card screen. From Paused, an arrow labelled "card updated, charge clears" leads up and right to the same Recovered state, showing pausing is reversible. Separately, a "Cancelled" state sits to the far right, reachable only by an arrow labelled "human acts in billing tool" drawn from Paused with a person icon; a note marks that the system never draws this arrow itself. Below, the entitlement contract is shown: dunr-gatekeeper reads dunr-dunning and answers granted for Active, Retrying, and Recovered, and paused only for Paused. A note at the bottom reads: it never cancels on its own — pause is the strongest action, and it is reversible in the same second a payment clears. Active granted Retrying granted — retries + dunning emails charge fails Recovered restored instantly, remaining retries cancelled retry succeeds next billing cycle Paused paused — update-card screen final retry fails card updated, charge clears Cancelled human only It never cancels on its own — pause is the strongest action, reversible the same second a payment clears.
Fig 5. The access state machine. Granted through the whole retry window, paused only after the final retry fails, and restored the instant a payment clears. Cancellation is reachable only by a human acting in your billing tool.

Restore is instant, not overnight

The flip side of pausing carefully is restoring fast. Nothing frustrates a customer more than fixing their card and then still being locked out “while the system catches up”. There is no overnight batch here. Restoration happens on the same event-driven path as everything else:

  • A retry succeeds. The processor fires charge.succeeded; the front door routes it; the row flips to recovered; the next entitlement check returns granted. If the subscription was paused, the customer is back in the moment they refresh.
  • A card is updated. The customer taps the link and updates their card on the processor’s page; the payment_method.updated webhook tells the retrier to attempt the charge now rather than waiting for the next scheduled slot. The moment that charge clears, access is restored — often within seconds of them entering the new card.

In both cases dunr-gatekeeper needs no special wake-up: it always reads the live row, so the instant the row says recovered, the answer is granted. The only lag is your app’s own short cache on the entitlement answer, which is why that cache is kept to a minute or two.

Pause is not cancel

It’s worth stating the boundary plainly, because it’s the guardrail that defines this system. The strongest action dunning recovery takes on its own is to set a subscription to paused — a reversible flag that gates access and shows an update-card screen. It never deletes the subscription, never tells the processor to cancel it, never wipes the customer’s data. Cancellation is a separate, deliberate act that a human takes in your billing tool when they’ve decided this customer really has gone. When that happens the processor fires subscription.cancelled, and the system simply stands down — cancels any remaining retries, stops the emails, closes the row. The arrow into cancelled is the one arrow in the whole state machine the system never draws by itself.

Design rules that shaped access

  • One check, two answers. The app asks dunr-gatekeeper; it replies granted or paused.
  • Granted through retries. Nobody is locked out while you’re still trying to charge their card.
  • Paused only at the end. Access flips only after the final retry has genuinely failed.
  • Restore is instant. A cleared charge or updated card restores access in the same second — no batch.
  • Pause is reversible; cancel is human. The system never originates a cancellation.
  • Every flip is logged. dunr-log records each pause and restore with a before-and-after snapshot.

That’s the system end to end: caught, scheduled, emailed, paused, restored. The next post adds up what it all costs — and why the part that recovers the money is the cheapest part of the bill.

All posts