Skip to content

feat(retention): a lapsed subscription loses the features, not the history - #879

Merged
lilyshen0722 merged 1 commit into
mainfrom
feat/pro-data-grace
Aug 6, 2026
Merged

feat(retention): a lapsed subscription loses the features, not the history#879
lilyshen0722 merged 1 commit into
mainfrom
feat/pro-data-grace

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Sam's call: "at time of expiry, we shouldn't immediately remove their message,
we should keep another 30 days."

Right, and the trigger is worse than deliberate cancellation. statusGrantsPro
returns false for past_due, so one failed card payment flips the
entitlement — and #878's protection disappears with it, meaning that night's
retention run permanently deletes everything older than 30 days. Before the
customer has read the dunning email. With nothing to recover if they fix the
card the next morning.

That is the worst possible moment to be destructive. A lapsed customer is
exactly who you want back; winning them back is impossible once their history
is gone; holding the bytes another month costs almost nothing.

Shape

Features stop immediately. Data gets a grace window. The entitlement still
flips to false at once, so Community listing and the rest lock the same
instant — the grace covers bytes, not access. There's a test pinning that
split, because it would be easy to "fix" this by leaving pro true and
accidentally give away the paid features for a month.

billing.proEndedAt is the clock, stamped by applySubscriptionState on the
true → false edge only. Stripe retries for three days and dunning fires
repeatedly; re-stamping on every past_due would walk the deadline forward
forever and the history would never be reclaimed. Cleared on re-subscribe, so
a later lapse gets a fresh window rather than an expired one.

Two deliberate choices

The window is not derived from PG_MESSAGE_RETENTION_DAYS. That window is
the free tier's product; this one is a win-back runway. Tying them would mean
that squeezing free-tier storage silently shortens a paying customer's grace.

A malformed PRO_DATA_GRACE_DAYS falls back to 30, not 0. The failure mode
of that particular typo is deleting a paying customer's history tonight, so it
fails toward keeping data. Tested.

Verification

Nine tests across both services. Reverted the change and confirmed six fail.
71 billing + retention tests pass, typecheck clean.

Related: #875, #877, #878.

…story

Losing Pro currently means losing everything older than 30 days on that
night's retention run. The trigger is not only a deliberate cancellation:
`statusGrantsPro` returns false for `past_due`, so ONE failed card payment is
enough — the deletion lands before the customer has read the dunning email,
and fixing the card the next morning brings nothing back.

That is the worst possible moment to be destructive. A lapsed customer is
someone we want back, winning them back is impossible once their history is
gone, and holding the bytes for another month costs almost nothing.

Features stop immediately; data gets PRO_DATA_GRACE_DAYS (default 30, env
overridable). `billing.proEndedAt` is the clock, stamped by
applySubscriptionState on the true -> false EDGE only — Stripe retries for
three days and dunning fires repeatedly, so re-stamping on every past_due
would walk the deadline forward forever and the history would never be
reclaimed. Cleared on re-subscribe so a later lapse gets a fresh window.

The grace window is deliberately NOT derived from PG_MESSAGE_RETENTION_DAYS:
that window is the free tier's product, this one is a win-back runway, and
squeezing free-tier storage must never shorten it. A malformed env value falls
back to 30 rather than collapsing to zero, because the failure mode of that
mistake is deleting a paying customer's history tonight.

Nine tests; verified by reverting the change and watching six fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8
@lilyshen0722
lilyshen0722 merged commit aac4425 into main Aug 6, 2026
11 checks passed
@lilyshen0722
lilyshen0722 deleted the feat/pro-data-grace branch August 6, 2026 10:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant