Documentation
¶
Overview ¶
Package router implements the pure channel-routing function from the notification-preferences paper §4. Given a user's resolved preferences and a notification category, it returns the destination channel set for a single send.
The function is pure: no IO, no DB reads, no clock except the caller-supplied `now`. Composability over inheritance — the caller resolves prefs (via the preferences CRUD) and then invokes DeliveryChannels.
Phase 1 returns Email / SMS / Web only. The Channel type still declares WA + Push so the wire shape is forward-compatible, but the router never returns them — they are Phase 2 / Phase 3.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Category ¶
type Category string
Category is one of the canonical notification categories from §3 of the paper. Categories split into two regulatory regimes:
- transactional (security, legal, money_movement, trade_execution, account_status, system) — email always fires; SMS/WA/web/push opt-in for real-time courtesy.
- marketing (promotional, newsletter, product_update, partner_offer, research) — per-category opt-in required by CAN-SPAM + GDPR + TCPA.
const ( CatSecurity Category = "security" CatLegal Category = "legal" CatMoneyMovement Category = "money_movement" CatTradeExecution Category = "trade_execution" CatAccountStatus Category = "account_status" CatSystem Category = "system" CatPromotional Category = "promotional" CatNewsletter Category = "newsletter" CatProductUpdate Category = "product_update" CatPartnerOffer Category = "partner_offer" CatResearch Category = "research" )
type Channel ¶
type Channel string
Channel is one of the canonical destination channels. Email + SMS + Web are wired in Phase 1; WA + Push are declared so the wire shape is stable but the router never returns them in Phase 1.
func DeliveryChannels ¶
func DeliveryChannels(prefs *Preferences, cat Category, now time.Time) []Channel
DeliveryChannels picks the destination channel set for a single send. Pure: no IO, no DB reads. `now` is the wall-clock instant used to evaluate quiet hours; callers pass time.Now().UTC() in production and a fixed instant in tests.
Behaviour from §4:
- Mandatory-email categories: always include email. For real-time transactional categories, additionally include any prefs.RealtimeChannels the matrix allows AND the user has contact for.
- Marketing categories: empty set unless the user has explicitly opted in. Respect quiet hours for promotional/partner_offer.
- Unknown categories: panic. The paper §4 footer is explicit — "every category must be classed transactional or marketing".
type Preferences ¶
type Preferences struct {
UserID string
OrgID string
PrimaryEmail string
BackupEmail string
PrimaryPhone string
WhatsAppPhone string
LegalEmail string
// PreferredChannels — ordered list the user prefers for non-
// mandatory notifications.
PreferredChannels []Channel
// RealtimeChannels — real-time courtesy channels for transactional
// categories. The email leg is implicit and always fires for
// mandatory-email categories regardless of this field.
RealtimeChannels []Channel
// MarketingSubscriptions — per-category opt-in. Missing key = false.
MarketingSubscriptions map[Category]bool
QuietHoursStart string // "21:00"
QuietHoursEnd string // "08:00"
Timezone string // IANA, e.g. "America/New_York"
MarketingGloballyMuted bool
}
Preferences is the resolved per-(user, org) state the router reads. The CRUD layer materialises this from the notification_preferences row; the router itself never touches a database.