Skip to content

feat(messaging): digest batching for notifications (ADR-0030 P3b-2) - #1582

Merged
xuyushun441-sys merged 1 commit into
mainfrom
feat/notification-digest
Jun 4, 2026
Merged

xuyushun441-sys merged 1 commit into
mainfrom
feat/notification-digest

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Collaborator

Completes ADR-0030 P3b-2 — the digest middleware the platform declared in P2 (sys_notification_preference.digest) but never consumed.

What

Recipients can batch a topic into a daily / weekly digest instead of getting every notification immediately. Builds directly on P3b-1's deferral seam.

  • PreferenceResolver now reads the digest field. digestDeferral() defers a batched recipient to the next window (local midnight for daily, Monday 00:00 for weekly) and tags the target with a stable window label. Digest takes precedence over quiet-hours; critical and mandatory topics bypass it entirely.
  • sys_notification_delivery gains digest_key (recipient|channel|window) + a grouping index. Batched rows partition by that key so a window's rows co-locate in one partition; the normal outbox claim() skips them and a new claimDigest() drains a window whole (implemented in both memory + sql outbox).
  • Dispatcher digest pass: under the existing per-partition cluster lock, it claims due batched rows, groups by digest_key, and collapses each (recipient, channel, window) group into one renderDigest() message, then acks every row in the group with that single outcome (failure re-defers the whole group).

Partition-by-digest_key + the existing lock means exactly one node assembles each digest — no double-send across a cluster.

Scope

Additive: non-digest notifications are completely unchanged. Deferred follow-ups (noted in the ADR): timezone fallback to a sys_user field (windows currently use quiet_hours.tz → UTC), a configurable daily send-hour, and MJML digest emails.

Tests

+13 (127 total in service-messaging): digestDeferral daily/weekly + tz; resolver stamps window + precedence + critical bypass; renderDigest collapse; outbox claim/claimDigest separation + window deferral; end-to-end dispatcher collapse (3 rows → 1 message, all acked) + group re-defer on failure.

🤖 Generated with Claude Code

Recipients can batch a topic into a daily/weekly digest instead of receiving
every notification immediately. Builds on P3b-1's deferral seam:

- PreferenceResolver consumes the `digest` field; digestDeferral() defers a
  batched recipient to the next window (local midnight / Monday 00:00) and tags
  the target with a stable window. Digest takes precedence over quiet-hours;
  critical + mandatory topics bypass it.
- sys_notification_delivery gains digest_key (recipient|channel|window). Batched
  rows partition by that key so a window co-locates; normal claim() skips them and
  the new claimDigest() drains a window whole (memory + sql outbox).
- The dispatcher's digest pass collapses each (recipient, channel, window) group
  into one renderDigest() message under the per-partition lock, then acks every
  row in the group with that single outcome.

Additive — non-digest delivery is unchanged. +13 tests (127 total). tz-from-
sys_user, configurable send-hour, and MJML digest emails are deferred follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 4, 2026 2:21pm

Request Review

This branch was successfully deployed

1 active deployment
Preview — 82e659ed Deployed Jun 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants