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 injectableClock.remove(phoneNumber)— opt-in helper.has(phoneNumber)andget(phoneNumber)— predicate + detail accessors.list()snapshot,size(), andclear()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 OptOutRegistryconstructor(options?: { clock?: Clock })add(phoneNumber: string, channel?: string): OptOutEntryremove(phoneNumber: string): booleanhas(phoneNumber: string): booleanget(phoneNumber: string): OptOutEntry | undefinedlist(): ReadonlyArray<OptOutEntry>importEntries(entries: ReadonlyArray<OptOutEntry>): voidclear(): voidsize(): 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.