Notification
Data Entity
Description
Persisted notification records for each user, covering all channels (push, email, SMS, in-app). Stores delivery status, read state, scenario origin, and a deep-link payload so the inbox screen and badge widget can reconstruct context without additional queries.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Primary key, generated server-side on creation. | PKrequiredunique |
user_id |
uuid |
Owner of this notification. Foreign key to users. | required |
channel |
enum |
Delivery channel used for this notification record. | required |
scenario |
string |
Identifier of the notification scenario that triggered this record (e.g. 'assignment_received', 'activity_approved', 'certificate_expiring'). Matches the scenario registry. | required |
title |
string |
Short notification title shown in the inbox and push alert. | required |
body |
text |
Full notification body text. Supports line breaks; rendered as plain text in push and SMS. | required |
data |
json |
Structured deep-link payload. Keys: route (string), reference_type (string), reference_id (uuid). Used by the notification message handler to navigate on tap. | - |
priority |
enum |
Dispatch priority affecting FCM/APNs urgency and inbox sort order. | required |
status |
enum |
Lifecycle state of the notification. | required |
failure_reason |
string |
Human-readable reason when status = failed (e.g. 'invalid_push_token', 'email_bounce'). Null otherwise. | - |
reference_type |
string |
Entity type that this notification relates to (e.g. 'assignment', 'activity', 'event', 'expense_claim', 'certificate'). Null for system-wide notifications. | - |
reference_id |
uuid |
ID of the referenced entity. Used together with reference_type for deep linking. | - |
sent_at |
datetime |
Timestamp when the notification was handed off to FCM, email provider, or SMS gateway. Null until dispatched. | - |
delivered_at |
datetime |
Timestamp confirmed by FCM delivery receipt or email open pixel. Null until confirmed. | - |
read_at |
datetime |
Timestamp when the user tapped or dismissed the notification in the inbox. Null until read. | - |
expires_at |
datetime |
Optional expiry after which the notification is no longer actionable (e.g. event sign-up deadline passed). Null = no expiry. | - |
created_at |
datetime |
Server-side creation timestamp. | required |
updated_at |
datetime |
Last status update timestamp. Updated on every status transition. | required |
Database Indexes
idx_notifications_user_created
Columns: user_id, created_at
idx_notifications_user_status
Columns: user_id, status
idx_notifications_reference
Columns: reference_type, reference_id
idx_notifications_scenario
Columns: scenario, user_id
idx_notifications_expires_at
Columns: expires_at
Validation Rules
title_not_empty
error
Validation failed
body_not_empty
error
Validation failed
user_id_exists
error
Validation failed
channel_enum_valid
error
Validation failed
scenario_registered
error
Validation failed
reference_pair_complete
error
Validation failed
data_payload_schema
error
Validation failed
expires_at_in_future
error
Validation failed
read_at_requires_delivered_status
error
Validation failed
Business Rules
quiet_hours_enforcement
Push notifications must not be dispatched during a user's configured quiet hours. The scenario evaluation job checks the user's notification_settings row before dispatching. If within quiet hours, dispatch is deferred to the next allowed window unless priority = high (e.g. encrypted assignment received).
channel_opt_out_respected
If the user has disabled a channel (push/email/sms) for a given scenario in notification_settings, no record is created for that channel. The scenario dispatcher checks enabled channels before writing.
deduplication_cooldown
The dispatch deduplication guard blocks creation of a duplicate notification for the same (user_id, scenario, reference_id) within the cooldown window defined in notification_cooldown_log_table. Prevents alert storms from repeated triggers.
status_transition_monotonic
Status transitions follow a strict sequence: pending → sent → delivered → read. A notification already in delivered state cannot be set back to sent. failed is a terminal state for the dispatch attempt; the record may be retried by creating a new record.
user_scoped_access
A user may only read or update their own notifications. API endpoints enforce user_id = authenticated user. Coordinators and admins do not have access to another user's notification inbox.
expired_notifications_hidden
Notifications past expires_at are excluded from inbox queries and badge counts even if status != read. They are retained in the table for audit but not surfaced in the UI.
high_priority_bypass_quiet_hours
Notifications with priority = high (e.g. encrypted assignment dispatch, threshold alert) bypass quiet hours and are delivered immediately regardless of user preference.