Skip to content

F3: User Preference Store & Routing Rules (NTF-003, NTF-084, NTF-085, NTF-087) ​

1. Technical Summary & Behavior ​

Per-user preference store defining global opt-out, channel-level opt-ins, category preferences, and quiet hours configuration.

  • Feature ID: F3
  • Source Files: backend/src/models/store.py, backend/src/preferences/routing.py
  • Key Implementation Lines: store.py:131-195 (UserPreferenceRecord), preferences/routing.py:98-160 (evaluate_dispatch)
  • Data & Schema: user_preferences (id, tenant_id, user_id, opt_out_all, channel_opt_in_json, categories_opt_in_json, preferred_channel, max_daily_notifications, quiet_hours_start, quiet_hours_end, timezone, updated_at)

2. Architecture & Data Flow ​


3. Sequence Flow Diagram ​


4. Failure Path & Resilience Diagram ​


5. Security & Edge Case Considerations ​

  • Tenant Isolation: Every query and operation scopes explicitly to tenant_id. Cross-tenant data leakage is structurally prevented.
  • Data Sanitization & Encryption: Sensitive recipient tokens and provider secrets are encrypted with Fernet and masked on all outputs.
  • Idempotency: Requests enforce unique constraint hashes to prevent duplicate dispatch under network retries.

6. Say It Out Loud (Spoken Interview Script) ​

"I built the user preference store to give recipients granular control over their messaging experience and ensure regulatory compliance with anti-spam laws. The store maintains per-user preferences including global opt-out flags, channel-specific toggles for email and SMS, category opt-ins for marketing versus security alerts, and local timezone definitions. I opted to store category and channel maps as structured JSON fields inside the preference record, giving us schema flexibility without requiring migration scripts every time a product team adds a new notification category. If an unconfigured user receives an alert, the engine defaults to permissive delivery while logging a debug notice. A limitation is that high-frequency preference lookups create database read contention, which I would mitigate using a Redis cache layer with a 5-minute TTL."