Skip to content

fix(digest): keep a subscriber's opt-out and run the Clerk sweep once a day - #721

Merged
Makisuo merged 1 commit into
mainfrom
fix/01-digest-opt-out-and-sweep-cadence
Sep 1, 2026
Merged

fix(digest): keep a subscriber's opt-out and run the Clerk sweep once a day#721
Makisuo merged 1 commit into
mainfrom
fix/01-digest-opt-out-and-sweep-cadence

Conversation

@Makisuo

@Makisuo Makisuo commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

First of a five-PR stack. Each PR is based on the one below it — review and merge in order. Every commit typechecks standalone.

The weekly digest is opt-out, so reconcileSubscriptions recreates a row for every current org member. It set enabled = true on conflict, which undid both ways a subscriber can turn the digest off:

  • upsertSubscription with enabled: false
  • deleteSubscription, whose row the very next sweep recreated enabled

enabled is now recomputed from a stored opted_out_at (migration 0053), so the sweep still re-enables a member it disabled itself when they left the org, but leaves a subscriber's own choice alone. deleteSubscription records that choice rather than erasing the only trace of it — a hard delete cannot survive an opt-out product.

The sweep's own 24-hour limiter never held either. It lived in a Ref owned by the service layer, and the alerting worker builds a fresh layer per cron invocation, so lastSyncAt started null on every tick. With the digest on the */15 cron that meant enumerating every Clerk org and member 96 times a day. It is pinned to the first tick of the UTC day now, which needs no state that survives an isolate.

Note for the deploy

Production migrations are applied manually — 0053_digest_opt_out.sql adds one nullable column.

Tests

Two regression tests in DigestService.test.ts, both verified to fail against the old behaviour.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

… a day

The weekly digest is opt-out, so `reconcileSubscriptions` recreates a row for
every current org member. It set `enabled = true` on conflict, which undid the
two ways a subscriber can turn the digest off: `upsertSubscription` with
`enabled: false`, and `deleteSubscription`, whose row the very next sweep
recreated enabled.

`enabled` now comes from a stored `opted_out_at`, so the sweep still re-enables
a member it disabled itself when they left the org, but leaves a subscriber's
own choice alone. `deleteSubscription` records that choice instead of erasing
the only trace of it.

The sweep's own 24-hour limiter never held either: it lived in a `Ref` owned by
the service layer, and the alerting worker builds a fresh layer per cron
invocation, so `lastSyncAt` started null on every tick. The digest cron fires
every 15 minutes, so the sweep enumerated every Clerk org and member 96 times a
day. It is now pinned to the first tick of the UTC day, which needs no state
that survives an isolate.
@Makisuo
Makisuo force-pushed the fix/01-digest-opt-out-and-sweep-cadence branch from 4994fb8 to 766a848 Compare September 1, 2026 14:51
@Makisuo
Makisuo merged commit ce761ba into main Sep 1, 2026
28 checks passed
@Makisuo
Makisuo deleted the fix/01-digest-opt-out-and-sweep-cadence branch September 1, 2026 15:33
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🍁 Maple PR preview

Warning

Preview cleanup could not be confirmed. The Alchemy teardown outcome was skipped.

Final commit 766a848 · View workflow run

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