-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Introduce a notificationService that serves as the public interface for all notification scheduling and delivery logic. It should abstract pgBoss job creation/execution and support email now, with a design that can extend to browser push / phone later.
Notifications should be scheduled primarily as event jobs (e.g., one job per class reminder) and resolve recipients + preferences at execution time in bulk, to avoid job fan-out and to ensure preference changes apply immediately.
Depends On
Goals
- Provide a stable, general-purpose API for scheduling/triggering notifications without leaking pgBoss details.
- Support scheduled and recurring notifications.
- Implement a default-safe preferences system:
- Global defaults per notification type (registry)
- User-specific overrides stored in a table
- No migrations/bulk updates required when adding new notification types
Public API (NotificationService)
Expose a small, stable interface:
-
notify({ type, audience, context, deliverAt, recurrence, idempotencyKey })- Used for immediate or scheduled notifications.
- The service decides the job strategy (event job vs per-user) internally.
-
cancel({ idempotencyKey }) -
reschedule({ idempotencyKey, deliverAt }) -
Preferences (for UI + enforcement):
setPreference({ userId, type, channel, enabled })clearPreferenceOverride({ userId, type, channel })getEffectivePreferences({ userId })(for ui)getAllEffectivePreferences({ type, userIds })getEffectivePreference({ type, userId })
Audience Shapes (initial)
{ kind: "user", userId }{ kind: "users", userIds: string[] }{ kind: "class", classId }
Default strategy: prefer event jobs for shared domain objects (e.g., one job per class reminder), and resolve recipients + preferences at execution time.
Job Strategy & Execution Model
Event Jobs (recommended default)
Example: class created -> schedule check-in reminder 15 minutes before start.
-
On scheduling:
- enqueue ONE job keyed by the domain object (e.g.,
classId + type + deliverAt) - persist/calculate an
idempotencyKeyfor cancellation/rescheduling
- enqueue ONE job keyed by the domain object (e.g.,
-
On execution:
- load the domain object (e.g., class) and validate it still applies (not canceled, still in time window, etc.)
- resolve recipients (volunteers/instructors/etc.)
- resolve preferences in bulk (single query per type/channel set)
- send via enabled channels
- write audit log / mark sent (idempotency)
Per-User Jobs (only when needed)
Reserved for truly individualized reminders where the audience is inherently per-user and job count is bounded.
Notification Type Registry (Defaults)
Define a central registry of notification types and their default channel behavior.
Example registry entry:
type: "available_shifts"- defaults:
- email: true
- push: true
This registry is the source of truth for:
- which channels exist for each type
- default enablement
- template selection / rendering hooks
Notification Preferences System
Drizzle Table: notification_preferences
Store only overrides. If no row exists, fall back to the registry default.
Columns:
userId(FK)type(notification type key; matches registry)channel(e.g.email,push)enabled(boolean)
Constraints & indexes:
- Unique constraint / PK on
(userId, type, channel) - Indexes for common lookup patterns:
(userId, type)or(userId, type, channel)(point lookup)- optionally
(type, userId)for fan-out resolution by type
Preference Resolution Logic
When deciding whether to deliver via a channel:
- check for override row
(userId, type, channel) - if missing, use registry default
(type, channel) - effective result determines send/no-send
Implementation note:
- For multi-recipient notifications, resolve overrides in bulk:
WHERE userId IN (...) AND type = ?- overlay onto defaults in memory
pgBoss Integration
- Use pgBoss for scheduling + retries.
- Jobs should include:
typeaudience(or a domain reference likeclassId)contextidempotencyKeydeliverAt/recurrencemetadata
Error handling:
- configure retry policies (attempts/backoff) suitable for email delivery