Skip to content

Smart Notifications — Detailed Technical Changes (2026-04-02)

This document provides a file-by-file breakdown of the Smart Notifications feature across all three repositories.

Summary

Smart Notifications is a multi-layered notification engine that sends personalized, AI-powered workout reminders and celebration messages to patients. The system spans 30+ files across the API, frontend, and mobile codebases.

See smart-notifications.md for the full architecture documentation.


OktaPT-API

New Service Files (src/services/smartNotifications/)

File Lines Purpose
dispatcher.ts 395 Core dispatch loop — runs every 15 minutes, finds eligible patients by checking if their target notification time falls in the current window, runs anti-annoyance filter, selects content, and enqueues to BullMQ
antiAnnoyanceFilter.ts 388 13-point decision checklist evaluated in order (first-block-wins). Checks: kill switch, mode off, paused, already completed, currently exercising, nothing scheduled, quiet hours, too early, daily cap, weekly cap, minimum gap, rest day, no channel. Lazy counter reset using DST-safe local day bounds
contentGenerator.ts 225 AI content generation via OpenAI (GPT-4 variants). Constructs system prompts with segment-specific tone, humor level instructions, and constraint enforcement. Produces 3 variants per call with title/pushBody/emailBody. All calls logged via aiResponseLogService
templateBank.ts 284 18 handwritten notification templates (3 per engagement segment) in English and Russian. Template selection avoids recently used variant IDs and categories. Slot interpolation via regex replacement
smartTimingCalculator.ts 227 Computes optimal notification time using trimmed circular weighted mean of last 21 workout start times. Exponential decay weighting (14-day half-life), 15% trimming, sin/cos circular mean for midnight wraparound, 30-minute lead time subtraction
engagementClassifier.ts 139 Classifies patients into 6 segments (NEVER_STARTED, EARLY, BUILDING, STREAKING, LAPSING, DORMANT) based on workout count, recency, streak length, and completion rate
profileRecomputation.ts 154 Nightly job (3:00 AM UTC) — iterates all enabled tenants and active patients, runs classifier + timing calculator, upserts SmartNotificationProfile. Handles expired admin pauses
contentPreGeneration.ts 223 Nightly job (3:30 AM UTC) — pre-generates AI content for all active profiles. Checks cache sufficiency (>= 3 unexpired variants), gathers slots for "tomorrow" accuracy, stores with 48h TTL. Cleans up expired cache
celebrationService.ts 226 Post-workout celebration handler. Idempotency check (one per local day), streak milestone detection (5, 10, 25, 50, 75, 100, 150, 200, 365), selects from milestone or standard celebration copy, enqueues as PROGRESS_CELEBRATION
channelSelector.ts 69 Segment-aware channel routing. EMAIL_FIRST for NEVER_STARTED, PUSH_FIRST for others. Checks per-type patient preferences and available channels (device token for push, email address for email)
notificationSender.ts 163 BullMQ job processor. Final freshness gate (skips if workout completed or in progress — celebrations bypass this). Calls notificationService.sendNotification(), updates profile counters and variety tracking (lastVariantIds, lastPersonalizationCategories)
timezoneUtils.ts 170 DST-safe timezone utilities. localMidnightToUtc (iterative offset correction), getLocalDayBounds (independent start/end midnights for 23h/25h DST days), getLocalTimeString, getLocalDayOfWeek, isInQuietWindow (handles overnight spans)
featureFlag.ts 17 Checks TenantSettings.features.smartNotifications
defaults.ts 43 Default cadence caps per segment (e.g., STREAKING: 1 daily / 5 weekly, DORMANT: 1 daily / 2 weekly), default channel strategy
index.ts 21 Barrel export for all smart notification services

New Routes (src/routes/smart-notifications/)

File Lines Purpose
index.ts 52 Route definitions — 2 patient endpoints (GET/PATCH profile) + 9 admin endpoints (tenants, policy CRUD, patient list/detail, overrides, test-generate, test-send, analytics)
controllers.ts 732 Controller logic for all endpoints. Key functions: getProfile, updateProfile, upsertPolicy (validates AI models), listPatients (paginated with profiles + streaks), testGenerate (gathers slots → ContentGenerator → 3 variants), testSend (enqueues test notification), getAnalytics (7-day suppression breakdown). Helper: resolveTestContext gathers comprehensive patient context

New Queue File

File Lines Purpose
src/queue/smartNotificationQueue.ts 53 BullMQ enqueue helper. Queue name: "smart-notification". Job ID: smart-notif-${userId}-${Date.now()}. Retry: 2 attempts with exponential backoff (5s initial). Dev fallback: processes directly if REDIS_URL not set

Modified Files

File Change
src/worker.ts Added Worker 4: smart-notification queue consumer with concurrency 3
src/services/notificationService.ts Added smart notification metadata support, device token validation
src/routes/user/controllers.ts Added registerDeviceToken, unregisterDeviceToken, getUserDeviceTokens controllers
src/routes/user/index.ts Added 3 device token routes (POST, DELETE, GET)
src/services/notifications/templates/feature-announcement.template.ts New branded email template for feature announcements

Prisma Schema Changes

Model Lines Purpose
DeviceToken 869–884 Mobile push token storage — token (unique), platform, deviceInfo (JSON), isActive, lastUsed
SmartNotificationProfile 917–956 Per-patient settings — mode, segment, intensity, timing, counters, variety tracking
SmartNotificationPolicy 958–984 Tenant-wide config — enabled, killSwitch, cadenceCaps, AI settings, channel strategy
SmartNotificationOverride 986–1014 Per-patient admin overrides — hard-lock mode/intensity/segment, pause with auto-resume
NotificationContentCache 1016–1038 Pre-generated AI content — variantId, content fields, 48h TTL, isUsed flag
NotificationLog 1040–1066 Audit trail — source, decisionReason, variantId, segmentAtSend

OktaPT-FE

New Files

File Lines Purpose
pages/admin/smart-notifications.tsx 1537 Three-tab admin page: Policy (tenant-level config with cadence caps, AI settings, channel strategy), Patient Overrides (per-patient config with pause/unpause), Analytics (suppression breakdown, segment distribution, sent/blocked counts)
pages/admin/notification-testing.tsx 657 Notification testing page — tenant/patient picker, content variable controls (type, segment, humor, locale, AI vs template), preview panel showing 3 variants with personalization slots, send-this-one buttons

Modified Files

File Change
pages/admin/dashboard.tsx Updated navigation menu with smart notifications and notification testing cards
pages/admin/navigation-menu.tsx Added Smart Notifications and Notification Testing cards to the Actions grid
middleware.ts No change to admin guard logic — new pages auto-protected by existing /admin prefix match

Okta-Mobile

New Files

File Lines Purpose
services/notificationService.ts 71 Expo Notifications service — permission requests, device token registration/unregistration, Android channel setup (MAX importance, vibration), AsyncStorage token caching, notification tap routing by type
components/NotificationPreferences.tsx ~200 Per-type notification toggles (6 types: workout reminder, workout assigned, daily reminder, admin message, doctor message, streak milestone) with channel selection (PUSH, EMAIL, SMS)
components/SmartNotificationSettings.tsx ~150 Patient-facing smart settings — mode picker (OFF/FIXED_TIME/SMART), intensity picker (MINIMAL/NORMAL/MOTIVATIONAL), quiet hours config, custom scroll wheel time picker
app/notifications-preferences.tsx ~30 Route page for notification preferences screen

Modified Files

File Change
services/api.ts Added 6 API methods: registerDeviceToken, unregisterDeviceToken, getUserDeviceTokens, getNotificationPreferences, updateNotificationPreference, getSmartNotificationProfile, updateSmartNotificationProfile
context/AuthContext.tsx Initializes notifications on app startup with cached credentials, user login, and signup completion via notificationService.initializeNotifications()
app/_layout.tsx Root layout sets up notification tap listeners via notificationService.initializeNotificationListeners()

Cross-Repo Integration Points

  1. Device Token Flow: Mobile registers token → API stores in DeviceToken → Dispatcher checks for active tokens when selecting channel → NotificationSender delivers via Expo push API
  2. Smart Settings Flow: Mobile SmartNotificationSettings → PATCH /v2/smart-notifications/profile → API updates SmartNotificationProfile → Dispatcher uses updated mode/timing
  3. Celebration Flow: API workout completion handler → CelebrationService → enqueueSmartNotification → Worker processes → notificationService.sendNotification → Expo push to mobile
  4. Admin Testing Flow: FE notification-testing page → POST /v2/smart-notifications/admin/test-generate → ContentGenerator or TemplateBank → 3 variants returned → Admin selects → POST /v2/smart-notifications/admin/test-send → BullMQ → delivery