Files
skills/docs/communication/messaging/sms/src/opt-out/opt-out.md
2026-05-29 08:20:56 +01:00

2.7 KiB

opt-out

(no submodules)

Purpose

opt-out is the in-memory opt-out registry that SmsService consults before every outbound message. Recipients on the registry receive no further messages — service.send throws SmsRecipientOptedOutError (code SMS.RECIPIENT_OPTED_OUT). Entries can be scoped to a channel so transactional messages remain deliverable when only marketing has been opted out by the host application.

The registry is intentionally pluggable but ships as an in-process Map so unit tests and small deployments need no external state; production hosts that share opt-out across processes can wrap OptOutRegistry in their own persistence layer.

Features

  • add(phoneNumber, channel?) — records the opt-out with a timestamp from an injectable Clock.
  • remove(phoneNumber) — opt-in helper.
  • has(phoneNumber) and get(phoneNumber) — predicate + detail accessors.
  • list() snapshot, size(), and clear() for bulk operations.
  • importEntries(entries) — bulk import with overwrite-by-id semantics for syncing from an external source.

Usage: Basic

import { OptOutRegistry } from "@suaveplan/sms/opt-out";

const reg = new OptOutRegistry();
reg.add("+15551234567", "marketing");
console.log(reg.has("+15551234567")); // true
reg.remove("+15551234567");

Usage: Advanced

import { OptOutRegistry } from "@suaveplan/sms/opt-out";

// Inject a deterministic clock for tests; importEntries
// reseeds the registry from a downstream cache.
const reg = new OptOutRegistry({ clock: () => 1_700_000_000_000 });
reg.importEntries([
    {
        phoneNumber: "+15550000001",
        optedOutAt: new Date(1_700_000_000_000),
        channel: "marketing",
    },
    {
        phoneNumber: "+15550000002",
        optedOutAt: new Date(1_700_000_000_000),
    },
]);
console.log(reg.size()); // 2

API Reference

  • class OptOutRegistry
    • constructor(options?: { clock?: Clock })
    • add(phoneNumber: string, channel?: string): OptOutEntry
    • remove(phoneNumber: string): boolean
    • has(phoneNumber: string): boolean
    • get(phoneNumber: string): OptOutEntry | undefined
    • list(): ReadonlyArray<OptOutEntry>
    • importEntries(entries: ReadonlyArray<OptOutEntry>): void
    • clear(): void
    • size(): number

Implementation Notes

The registry stores OptOutEntry records keyed by the verbatim phone number — no normalisation. Callers SHOULD pass E.164 strings; mismatched formats will fail to match. add overwrites existing entries by phoneNumber, so a caller switching the channel on an existing opt-out simply re-adds. The clock defaults to Date.now, deterministic only when explicitly injected.