Files
skills/docs/communication/messaging/notification-channel/src/preferences/preference-manager.md
2026-05-29 08:20:56 +01:00

3.1 KiB

preference-manager — Per-user opt-in / opt-out + quiet hours

(no submodules)

Purpose

preference-manager.ts ships the PreferenceManager class that answers, at every send-time, "should this notification reach this user on this channel right now?". The manager combines per-channel opt-in / opt-out preferences with optional quiet-hours configuration (with priority thresholds) and a pluggable PreferenceStoreAdapter for persistence.

Features

  • Per-user channel enable / disable, with explicit enabledChannels and disabledChannels sets.
  • Default channels (configurable; built-in default: ["email", "in_app"]).
  • Quiet-hours config with startHour, endHour (wrap-around supported), and optional days filter.
  • urgentThreshold controls which priorities punch through quiet hours (default urgent).
  • canDeliver(userId, channel, priority) is the single decision point.
  • Pluggable PreferenceStoreAdapter so persistence is delegated to SurrealDB, Postgres, Redis, etc.
  • Injectable Clock for deterministic tests.

Usage: Basic

import {
    PreferenceManager,
    type PreferenceStoreAdapter,
} from "@suaveplan/notification-channel";

const store: PreferenceStoreAdapter = makeStore();
const manager = new PreferenceManager({ store });

if (await manager.canDeliver("user-1", "email", "normal")) {
    await router.send(...);
}

Usage: Advanced

const manager = new PreferenceManager({
    store,
    defaultChannels: ["email", "push", "in_app"],
    clock: () => Date.now(),
});

await manager.setEnabled("user-1", "sms", true);
await manager.setQuietHours("user-1", {
    startHour: 22,
    endHour: 7,
    days: [1, 2, 3, 4, 5], // weekdays
});

// At dispatch time:
for (const channel of selected) {
    if (await manager.canDeliver("user-1", channel, "high")) {
        await router.send({ recipient: "user-1", body: text, channel });
    }
}

API Reference

  • PreferenceManager(options: PreferenceManagerOptions) — constructor.
  • PreferenceManagerOptions — { store, defaultChannels?, clock? }.
  • canDeliver(userId, channel, priority): Promise<boolean>.
  • getPreferences(userId): Promise<UserPreferences>.
  • setEnabled(userId, channel, enabled): Promise<void>.
  • setQuietHours(userId, quietHours | null): Promise<void>.
  • resetPreferences(userId): Promise<boolean>.
  • PreferenceStoreAdapter, UserPreferences, StoredPreferences, QuietHoursConfig.

Implementation Notes

Quiet-hours wrap around midnight when startHour > endHour (e.g. 22 → 7). Priorities are ordered low < normal < high < urgent; if urgentThreshold is "high", then both high and urgent punch through quiet hours. The day filter is opt-in — when omitted, quiet hours apply every day; when supplied, only the listed days (0 = Sunday) trigger the window. setQuietHours(userId, null) clears the field; existing enabledChannels/disabledChannels/urgentThreshold are preserved. The store adapter is the single integration seam — the rest of the class is purely functional logic, so swapping persistence layers does not change behaviour.