Skip to content

Notifications

The notification and communications system handles all in-app alerts, push notifications, emails, and preference management across DoCurious, balancing engagement with restraint -- especially for minors.

STATUS: BUILT

The in-app notification center, notification store, preference management UI, temporal grouping, mock API, email delivery (nodemailer with branded templates), per-category contextual nav badges, toast action buttons, quiet hours enforcement, and digest frequency support are all built. Push notifications and SMS are planned for post-launch.

Spec source: Doc 11 -- Notification & Communications System | Last updated: Feb 2026

Overview

Notifications in DoCurious exist to inform, not to nag. The system is designed around six guiding principles from the spec:

  1. Inform, don't nag -- notifications help users; they never create anxiety
  2. Respect attention -- especially for minors. No dark patterns, no urgency manipulation
  3. Channel-appropriate -- right message, right channel, right time
  4. User control -- every notification type can be individually configured
  5. COPPA-aware -- under-13 communications go through parents where required
  6. CAN-SPAM compliant -- all marketing emails include unsubscribe and physical address

The system uses an event-driven model: platform events (a Track Record getting verified, a gift being sent, a badge being earned) flow through a notification service that resolves recipients, checks preferences, applies COPPA rules, deduplicates, enforces frequency caps and quiet hours, selects channels, renders templates, delivers, and tracks results.

Architecture Flow

[Platform Event] --> [Notification Service] --> [Channel Router] --> [Delivery]
                          |                          |
                    [User Preferences]         [In-App / Push / Email]
                    [COPPA Checks]             [Template Engine]
                    [Frequency Caps]           [Delivery Queue]
                    [Quiet Hours]              [Tracking]

Processing Pipeline

Each notification passes through these steps in order:

  1. Event emitted -- e.g., track_record_verified
  2. Recipient resolution -- who should be notified?
  3. Preference check -- has the recipient opted out of this type?
  4. COPPA check -- is the recipient under 13? Route through parent?
  5. Deduplication -- has this exact notification been sent recently?
  6. Frequency cap check -- has the recipient exceeded notification limits?
  7. Quiet hours check -- is it within the recipient's quiet hours?
  8. Channel selection -- in-app always; push/email based on preferences
  9. Template rendering -- populate template with event data
  10. Delivery -- send via appropriate channel
  11. Tracking -- log delivery, opens, clicks

How It Works

Priority Levels BUILT

Every notification has a priority that determines delivery behavior. The type system defines four levels in NotificationPriority:

PriorityBehaviorExamples
CriticalAlways delivered immediately, all channels. Bypasses quiet hours.Account security alerts, account suspension
HighDelivered promptly, respects quiet hoursTR verified, assignment due soon, gift received
MediumBatched in digest if user prefersNew community post, challenge recommendation, streak reminder
LowDigest only, never pushPlatform announcements, feature tips, weekly summary

File: src/types/notification.types.ts -- defines NotificationPriority as 'low' | 'medium' | 'high' | 'critical'

Delivery Channels PARTIAL

The spec defines four delivery channels. Currently only in-app is fully operational:

ChannelStatusDescription
In-AppBuiltBell icon notification center, full-page notification list, unread badges
PushNot startedWeb Push API via Service Worker (VAPID auth). Mobile push planned for native apps.
EmailNot startedTransactional + digest emails via ESP (SendGrid, Postmark, or AWS SES)
SMSPlannedFuture channel, no spec detail yet

File: src/types/notification.types.ts -- defines NotificationChannel as 'in_app' | 'email' | 'push'

Notification Categories BUILT

The type system defines 13 notification categories. Each maps to a toggle group in the preferences UI, and role-specific categories are only shown to users with the matching role:

CategoryDescriptionRole-Specific?
challenge_updatesChallenge status changes, new assignmentsNo
social_activityComments, thumbs up, community activityNo
community_activityNew posts, member activityNo
remindersDue dates, incomplete challengesNo
digestsDaily/weekly summariesNo
systemAccount, security, platform updatesNo
giftsGift received, gift remindersNo
verificationTR approved/rejectedNo
teacher_feedbackTeacher comments on TRsNo
gamificationXP gains, level ups, badge earnedNo
parent_alertsChild activity, consent requestsParent only
school_adminRoster changes, compliance alertsSA only
vendorChallenge performance, payoutsVendor only

File: src/types/notification.types.ts -- NotificationCategory type union

In-App Notification Center BUILT

The notification center is the primary delivery surface. It consists of two components:

Bell icon dropdown (NotificationCenter.tsx): Lives in the global header. Shows a badge count of unread notifications (capped at "99+"). Clicking opens a dropdown panel with the 10 most recent notifications. Each item shows a category icon, title, message preview, and relative timestamp. Includes "Mark all read" and a link to notification settings. Polls for updated counts every 60 seconds.

Full-page list (Notifications.tsx): Accessible via "View all notifications" link in the dropdown or direct navigation to /notifications. Shows all notifications with All/Unread filter tabs. Supports pagination (20 per page) with "Load More." Each notification can be clicked to mark as read and navigate to related content. Individual notifications can be deleted.

Key files:

  • src/components/notifications/NotificationCenter.tsx -- bell icon dropdown component
  • src/pages/Notifications.tsx -- full-page notification list
  • src/store/useNotificationStore.ts -- Zustand store with pagination, read/unread state, and preference management
  • src/api/notification.api.ts -- mock API with CRUD operations
  • src/api/notification.real.api.ts -- real backend API stubs

Notification Data Model BUILT

Each in-app notification contains:

FieldTypeDescription
userIdUUIDRecipient
categoryNotificationCategoryWhich category this belongs to
priorityNotificationPriorityDelivery urgency
titlestringShort headline
messagestringLonger description
actionUrlstring (optional)Deep-link URL to related content
actionLabelstring (optional)Button label for the CTA
relatedTypeenum (optional)Entity type: challenge, track_record, community, user, gift, assignment
relatedIdUUID (optional)ID of related entity
isReadbooleanWhether user has seen this
readAttimestamp (optional)When it was read
sentViaEmailbooleanWhether also sent by email
sentViaPushbooleanWhether also sent by push

File: src/types/notification.types.ts -- Notification interface extending BaseEntity

In-App Toast Notifications PARTIAL

The spec calls for slide-in toast notifications for high-priority events that occur while the user is active. The current codebase has an XPGainToast component for gamification XP gains (animated gradient card with pop-in and shimmer effects), but a general-purpose toast system for arbitrary notification types is not yet built.

Spec requirements for the general toast system:

  • Slide-in from top-right on desktop, top on mobile
  • Auto-dismiss after 5 seconds
  • Click to navigate to content
  • Dismiss button
  • Maximum 1 toast at a time (queue if multiple)

File: src/components/gamification/XPGainToast.tsx -- XP-specific toast (built)

Contextual Indicators NOT STARTED

Beyond the notification center, the spec calls for badge counts on relevant page tabs:

  • Badge count on "My Challenges" tab when new verification results arrive
  • Badge count on "Communities" tab when new posts appear
  • Indicator on assignments section when new assignments or feedback come in
  • Indicators clear when the user visits the relevant section

These contextual indicators are not yet implemented.

Celebration Overlays NOT STARTED

Celebration overlays are distinct from notifications -- they are delightful full-screen or modal moments, not informational items. The spec defines them for:

  • Level up
  • Badge earned
  • Challenge completed
  • Learning Path completed
  • Streak milestone

These are specified in Doc 8 (Gamification) and are not yet implemented. The celebrationAnimations toggle in notification preferences (which allows users to turn them off) is defined in the data model but has no UI to control yet.

Push Notifications NOT STARTED

No Service Worker, Web Push API, or push permission logic exists in the codebase. The spec defines a thoughtful permission strategy:

When NOT to request push permission:

  • First visit
  • During onboarding
  • Immediately after registration

When TO request push permission:

  • User completes their first challenge (natural engagement moment)
  • User enables a streak (to get opted-in reminders)
  • User navigates to notification settings (explicit interest)

Permission request copy example:

"Want to know when your Track Record is verified? Enable notifications so you don't miss it."

The copy should be contextual and benefit-specific, never generic "allow notifications?"

Push content rules:

  • Title: 50 characters max
  • Body: 100 characters max
  • No clickbait or urgency manipulation
  • Deep link to relevant content
  • Under-13 users: push only for school assignments and verification results

Email System NOT STARTED

The spec defines a comprehensive email system with 36 templates across 4 categories. No email infrastructure exists in the codebase yet.

Email Categories

CategoryUnsubscribe?DescriptionTemplate Count
TransactionalCannot unsubscribeLegally required or essential: verification, password reset, security alerts, deletion confirmation7
ServiceGranular unsubscribeRelated to active use: TR results, assignments, gifts, event reminders12
DigestUnsubscribeSummary communications: weekly user/parent/teacher/vendor summaries, monthly school reports5
Marketing/EngagementUnsubscribeRe-engagement and feature promotion. Never sent to under-13 users.4

Email Sending Limits

  • No more than 1 non-transactional email per user per day (excluding digests)
  • Digests: maximum 1 per week per type
  • Re-engagement: maximum 1 per 30 days
  • Under-13: only transactional and school-related service emails

Email Design Rules

  • Mobile-responsive (single column at < 600px), max width 600px
  • System fonts only (no custom font loading)
  • Images: always include alt text, never rely on images for key info
  • CTA buttons: minimum 44px tap target
  • Dark mode compatible
  • Plain text version for every HTML email
  • Tone: warm, encouraging, never guilt-inducing

Notification Preferences BUILT

Users can configure notifications through a preferences UI accessible from the notification dropdown (gear icon) or from Account Settings > Notifications.

The preferences component provides:

  • Per-category toggles -- a grid with In-App, Email, and Push columns for each of 6 visible categories (Challenges, Communities, Social, School, Gamification, System)
  • Bulk enable/disable -- "Enable all" / "Disable all" links per channel column
  • Quiet hours -- toggle with configurable start and end time
  • Email digest frequency -- Real-time, Daily Digest, or Weekly Digest radio buttons
  • Save button with loading state and success confirmation

The preferences component is connected to the Zustand store (useNotificationStore), calling fetchPreferences on mount and updatePreferences on save. Quiet hours are included in the preferences payload.

Key files:

  • src/components/account/NotificationPreferences.tsx -- preferences UI component
  • src/store/useNotificationStore.ts -- store with preferences state and updatePreferences action
  • src/types/notification.types.ts -- NotificationPreferences interface and defaultNotificationPreferences factory defaults

Quiet Hours BUILT (UI only)

Quiet hours suppress non-critical push notifications and emails during a configurable time window. The UI for setting quiet hours is built. Server-side enforcement is not.

SettingDefaultNotes
EnabledOffUser must opt in
Start time22:00 (10 PM)Configurable
End time08:00 (8 AM)Configurable
TimezoneAmerica/New_YorkSet at registration, adjustable
Applies toPush, non-critical emailsIn-app notifications are never suppressed
ExceptionsCritical notificationsSecurity alerts always bypass quiet hours

Frequency Caps NOT STARTED

The spec defines per-role push notification limits. When caps are reached, remaining notifications are delivered via in-app only.

User TypeMax Pushes Per DayMax Per Week
General user (18+)520
Minor (13-17)312
Under-1327
Parent520
Teacher525
SA315
Vendor520

No frequency cap enforcement exists in the codebase.

Notification Catalog

The spec defines 60+ notification events grouped by domain. Here is a summary of the major categories with their channel defaults:

Account & Security Events

EventPriorityChannelsUnder-13
Welcome / account createdHighIn-app, EmailParent receives copy
Password changedCriticalEmailParent notified
Password reset requestedCriticalEmailParent notified
New login from unrecognized deviceCriticalEmailParent notified
Account suspendedCriticalEmailParent notified

Challenge & Track Record Events

EventPriorityChannelsUnder-13
TR verified / approvedHighIn-app, Push, EmailParent notified
TR revision requestedHighIn-app, Push, EmailParent notified
All milestones completed (ready to submit)HighIn-app, PushStandard
Dealer's Choice card availableMediumIn-app, PushStandard

Assignment Events (School Context)

EventPriorityChannelsUnder-13
New assignmentHighIn-app, Push, EmailStandard (school)
Assignment due tomorrowHighIn-app, PushStandard
Assignment overdueHighIn-app, EmailParent notified
Assignment feedback receivedHighIn-app, Push, EmailParent notified

Gift Events

EventPriorityChannelsUnder-13
Gift receivedHighIn-app, Push, EmailParent approval required
Gift acceptedMediumIn-appStandard
Gift expiring soon (7 days)MediumIn-app, PushParent notified

Gamification Events

EventPriorityChannelsUnder-13
Level upHighIn-app, PushStandard
Badge earnedHighIn-app, PushStandard
Rare/Legendary badge earnedHighIn-app, Push, EmailStandard
Streak milestone (7, 14, 30, etc.)HighIn-app, PushStandard
Streak at risk (1 day remaining)MediumIn-app, PushStandard

Community Events

EventPriorityChannelsUnder-13
Invited to communityHighIn-app, PushParent approval required
Community goal achievedHighIn-app, PushStandard
New post in communityLowDigest onlyStandard
Removed from communityHighIn-app, EmailParent notified

See the full catalog of 60+ events in Doc 11, Sections 3.1--3.11.

Roles & Permissions

RoleReceives NotificationsConfigures PreferencesReceives CopiesSpecial Rules
General UserYesFull control over categories and channelsN/AStandard frequency caps
Student (Tier 1)Yes, limitedCannot configure (school-managed)Parent gets copiesNo marketing, no community, minimal push
Student (Tier 2)Yes, limitedParent approves preferencesParent gets copiesNo marketing, achievement in-app only
ParentYes + child copiesFull controlCopies of all child communicationsApproval requests for sharing, community
TeacherYesFull controlN/AExtra: verification queue alerts, class summaries
School AdminYesFull controlN/AExtra: roster alerts, health score, maintenance
VendorYesFull controlN/AExtra: challenge performance, event registrations
Platform AdminYesFull controlN/AExtra: security alerts, system health, content flags

Default Preferences by Role

CategoryIn-AppPush (General)Email (General)Push (Minor)Email (Minor)
Challenge updatesAlways onOnOnOnOff
Verification resultsAlways onOnOnOnOn
AssignmentsAlways onOnOnOnOn
GiftsAlways onOnOnOnOff
CommunitiesAlways onOffOffOffOff
AchievementsAlways onOnOffOnOff
Streak remindersAlways onOnOffOffOff
RecommendationsAlways onOffOffOffOff
Weekly summaryN/AN/AOnN/AOff
Re-engagementN/AN/AOnN/ANever sent

In-app notifications cannot be turned off -- they are the minimum delivery channel.

Constraints & Limits

ConstraintValueSource
Notification page size20 per pageStore implementation
Bell dropdown limit10 most recentNotificationCenter component
Bell count cap99+ displayNotificationCenter component
Polling interval60 secondsNotificationCenter component
Toast auto-dismiss5 secondsSpec (not yet implemented)
Max simultaneous toasts1 (queue others)Spec (not yet implemented)
Push title length50 characters maxSpec
Push body length100 characters maxSpec
Non-transactional email limit1 per user per daySpec
Digest frequencyMax 1 per week per typeSpec
Re-engagement emailMax 1 per 30 daysSpec
Quiet hours default9 PM -- 7 AM (spec) / 10 PM -- 8 AM (code)Slight discrepancy
Email verification link expiry24 hoursSpec
Password reset link expiry1 hourSpec
Data export download link expiry48 hoursSpec
Account deletion grace period30 daysSpec
Unsubscribe processingInstant (target), 10 business days (CAN-SPAM max)Spec

COPPA & Minor Communication Rules

Under-13 (Tier 1 -- School Only)

What IS sent: Assignment notifications (in-app, limited push), TR verification results (in-app, push), security alerts (in-app).

What is NOT sent: Marketing or re-engagement emails, streak pressure notifications, community notifications (no community access), recommendation push notifications.

Parent receives: Copy of all communications sent to child, weekly activity summary, assignment status updates, all approval requests.

Under-13 (Tier 2 -- Parent-Linked)

Same as Tier 1, plus: gift notifications (routed through parent for approval), achievement notifications (in-app only). Parent must approve communication preferences.

Ages 13--17

Standard notifications with restrictions:

  • No re-engagement emails
  • Reduced push frequency caps (3/day, 12/week instead of 5/day, 20/week)
  • Streak reminders: softer language, limited to 1 per day
  • No FOMO-inducing language
  • Leaderboard notifications: only positive ("your achievement"), never "you're falling behind"

CAN-SPAM Compliance

All non-transactional emails must include:

  • Clear identification as commercial message (where applicable)
  • Valid physical postal address
  • Clear unsubscribe mechanism
  • Accurate "From" and "Subject" lines
  • No deceptive headers or subject lines

Design Decisions

  1. In-app is always on. In-app notifications cannot be turned off because they are the minimum viable channel. Every notification creates an in-app record regardless of other channel preferences. This guarantees users never miss critical information.

  2. Push permission is delayed. The spec explicitly prohibits requesting push permission on first visit, during onboarding, or immediately after registration. Permission is requested only at natural engagement moments (first challenge completed, streak enabled, or visiting notification settings). This respects user attention and improves opt-in rates.

  3. No marketing to under-13. Marketing and re-engagement emails are never sent to users under 13. This is a hard rule, not configurable. Even for users 13--17, re-engagement emails are blocked.

  4. Celebration overlays are not notifications. Level-ups, badges earned, and streak milestones produce celebration overlays (full-screen or modal animations) that are distinct from the notification system. They are joyful moments, not information items. Users can disable celebration animations through preferences.

  5. Quiet hours have exceptions. Critical notifications (security alerts, account suspension) bypass quiet hours. This ensures users are always informed of security-sensitive events regardless of their schedule.

  6. Frequency caps are role-aware. Minors receive fewer push notifications per day and per week than adults. Teachers have slightly higher weekly caps to accommodate classroom management needs.

  7. Digest over spam. The default email digest frequency is daily (in code) or weekly (in spec). Users can choose real-time, daily, weekly, or off. Empty digests are never sent -- if there is nothing meaningful to report, no email goes out.

  8. Preferences UI uses local state. The NotificationPreferences component currently manages its own local state with a mock save, rather than connecting to the Zustand store's updatePreferences action. This was a pragmatic decision during prototyping and should be wired up when the backend is ready.

  9. Category discrepancy between UI and types. The preferences UI shows 6 categories (Challenges, Communities, Social, School, Gamification, System), while the type system defines 13 categories. The additional categories (gifts, verification, teacher_feedback, parent_alerts, school_admin, vendor, digests) are typed but not yet surfaced in the preferences grid. Role-specific categories should only appear for users with the matching role.

  10. Quiet hours default discrepancy. The spec defines quiet hours as 9 PM -- 7 AM, but the code defaults to 10 PM -- 8 AM. This should be reconciled before launch.

Technical Implementation

Store

The useNotificationStore is a Zustand store managing:

  • Notification list with pagination (fetchNotifications, fetchMoreNotifications)
  • Unread counts with periodic polling (fetchCounts)
  • Read state management (markAsRead, markAllAsRead)
  • Deletion (deleteNotification)
  • Preferences (fetchPreferences, updatePreferences)
  • Loading and error states (isLoading, isLoadingMore, isSaving, error)

Exported selectors: selectNotifications, selectUnreadCount, selectNotificationCounts, selectPreferences.

File: src/store/useNotificationStore.ts

API Layer

Mock API (notification.api.ts): Full CRUD against the in-memory mockDb. Supports paginated listing, filtering by unread, marking as read (individual and bulk), deletion, preference get/set, and notification creation.

Real API (notification.real.api.ts): HTTP-based stubs calling the Express backend. getNotificationCounts and deleteNotification return stubs (no backend endpoint). Other operations map to REST endpoints at /notifications/*.

File: src/api/notification.api.ts, src/api/notification.real.api.ts

Components

ComponentLocationDescription
NotificationCentersrc/components/notifications/NotificationCenter.tsxBell icon dropdown in header with unread badge, 10-item preview, mark all read, settings link
Notifications (page)src/pages/Notifications.tsxFull-page notification list with All/Unread tabs, pagination, delete per item
NotificationPreferencessrc/components/account/NotificationPreferences.tsxPer-category toggle grid, quiet hours, digest frequency, save button
XPGainToastsrc/components/gamification/XPGainToast.tsxAnimated XP gain toast (gamification-specific, not general purpose)

Types

TypeDescription
NotificationIn-app notification entity with category, priority, read state, deep-link fields
NotificationCategory13-member string union for topic categorization
NotificationChannel'in_app' | 'email' | 'push'
NotificationPriority'low' | 'medium' | 'high' | 'critical'
NotificationPreferencesPer-category, per-channel toggle grid + quiet hours + digest frequency
NotificationCountsAggregate total/unread/byCategory counts
defaultNotificationPreferencesFactory defaults for new users

File: src/types/notification.types.ts

Backend Data Model (Planned)

The spec defines 5 database tables for the full notification system:

TablePurpose
notificationsIn-app notification records. Indexed on (user_id, read, created_at DESC)
notification_preferencesPer-user preference configuration. One row per user.
email_sendsEmail delivery tracking with ESP message IDs, delivery/open/click timestamps
email_suppressionsHard bounces, complaints, unsubscribes. Prevents future sends.
push_tokensPer-device push tokens (web, iOS, Android) with active/inactive state
push_sendsPush delivery tracking linked to notifications and tokens

What Needs Building

FeaturePriorityDependencies
Wire preferences UI to Zustand storeHighNone
Event-driven notification creationHighBackend event bus
COPPA-aware recipient routingHighUser age/role resolution
Push notification infrastructure (Service Worker, VAPID)MediumBackend push service
Email template system (36 templates)MediumESP integration (SendGrid/Postmark/SES)
Frequency cap enforcementMediumBackend notification service
Quiet hours server-side enforcementMediumBackend notification service
General-purpose toast systemMediumUI framework decision
Celebration overlays (level up, badge, streak)MediumGamification events
Contextual page-tab indicatorsLowPer-page unread counts
Delivery tracking and analyticsLowESP webhooks, push receipts
A/B testing on email templatesLowEmail system + analytics
SMS channelLowSMS provider integration
  • Challenges -- Challenge start, milestone completion, and TR submission events trigger notifications
  • Track Records -- TR verification, revision requests, and rejections are high-priority notification events
  • Gifting -- Gift sent, accepted, declined, completed, and expiring events all generate notifications with COPPA routing for under-13 recipients
  • Accounts -- Security alerts, password changes, and new device logins are critical notifications; account settings host the notification preferences UI
  • Explore -- Challenge recommendations can generate medium-priority in-app notifications
  • Gamification (Doc 8) -- Level ups, badges earned, streak milestones, and leaderboard changes trigger notifications and celebration overlays
  • Communities (Doc 5) -- Community invitations, posts, goal progress, and moderation warnings generate notifications
  • School Administration (Doc 6) -- Assignments, roster changes, and school health scores generate role-specific notifications for students, teachers, parents, and school admins

DoCurious Platform Documentation