core PK: id 10 required 1 unique

Description

Persisted notification records delivered to users via push, email, or SMS channels. Tracks delivery status, read state, and links to triggering domain events for audit and display in the notification inbox.

21
Attributes
8
Indexes
9
Validation Rules
19
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Primary key — globally unique notification record identifier
PKrequiredunique
user_id uuid FK → users.id — the recipient of this notification
required
organization_id uuid FK → organizations.id — tenant scope, used to enforce data isolation
required
channel enum Delivery channel used for this notification
required
type enum Scenario/trigger type that caused this notification to be created
required
title string Short notification title shown in inbox and push banner (max 120 chars)
required
body text Full notification body text. Rendered in notification inbox detail view.
required
deep_link string Optional URI to navigate the app to the relevant screen when notification is tapped (e.g. meander://assignment/abc123)
-
status enum Delivery lifecycle status of this notification record
required
read_at datetime Timestamp when the user explicitly opened or acknowledged the notification in the inbox. Null = unread.
-
sent_at datetime Timestamp when the notification was dispatched to the external provider (FCM, email relay, SMS gateway)
-
failed_at datetime Timestamp of terminal delivery failure. Populated alongside failure_reason.
-
failure_reason string Human-readable error from the delivery provider when status = failed
-
reference_type string Domain entity type the notification relates to (e.g. 'assignment', 'expense', 'event'). Used to resolve deep_link and display contextual detail.
-
reference_id uuid ID of the domain entity instance that triggered this notification (e.g. the assignment UUID). Nullable for system announcements.
-
scenario_id string Identifier of the Scenario Engine rule that generated this notification. Enables traceability back to scenario configuration.
-
metadata json Arbitrary key-value payload passed to the notification template renderer (e.g. peer_mentor_name, threshold_count). Stored as JSONB in PostgreSQL.
-
priority enum Delivery priority hint passed to push provider (FCM data vs notification message)
required
locale string BCP-47 locale code used to render the notification body (e.g. 'nb', 'en', 'se'). Supports Sami language notifications.
-
created_at datetime Record creation timestamp — when the notification was enqueued
required
expires_at datetime Optional TTL — notification will not be delivered after this timestamp. Used for time-sensitive alerts (e.g. event reminders).
-

Database Indexes

idx_notifications_user_id
btree

Columns: user_id

idx_notifications_user_status
btree

Columns: user_id, status

idx_notifications_user_read_at
btree

Columns: user_id, read_at

idx_notifications_organization_id
btree

Columns: organization_id

idx_notifications_type
btree

Columns: type

idx_notifications_reference
btree

Columns: reference_type, reference_id

idx_notifications_created_at
btree

Columns: created_at

idx_notifications_status_pending
btree

Columns: status, created_at

Validation Rules

valid_user_id error

Validation failed

title_length error

Validation failed

body_not_empty error

Validation failed

valid_channel_for_type error

Validation failed

valid_deep_link_format error

Validation failed

reference_consistency error

Validation failed

valid_locale error

Validation failed

status_transition_validity error

Validation failed

metadata_is_valid_json error

Validation failed

Business Rules

respect_user_preferences
on_create

Before creating a notification record and dispatching it, the scenario engine must check notification_preferences for the target user. If the user has disabled the relevant notification type or channel, the notification must not be created or sent.

tenant_isolation
always

Notifications may only be read or modified by users belonging to the same organization_id. API middleware must enforce this; cross-tenant access returns 403.

no_send_after_expiry
on_create

If expires_at is set and the current time exceeds it, the notification must be cancelled (status = cancelled) rather than dispatched. Prevents stale reminders for past events.

single_inbox_per_channel
on_create

For in_app channel notifications, only one unread notification per (user_id, type, reference_id) combination should be active at a time. Duplicate scenario triggers within 24 hours must be deduplicated to avoid inbox flooding.

assignment_reminder_timing
on_create

Assignment reminder notifications (type = assignment_reminder) must be triggered exactly 10 days after an assignment is dispatched if no read_at has been recorded for the corresponding assignment_dispatched notification. Governed by the assignment reminder scheduled job.

high_priority_push_only
on_create

Notifications with priority = high (e.g. assignment_dispatched with encrypted data) must always be delivered via push channel regardless of user channel preferences, to ensure timely receipt of sensitive assignments.

mark_read_idempotent
on_update

Marking a notification as read is idempotent. If read_at is already set, subsequent read updates must not overwrite the original timestamp.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
by_date
Retention
archive_after_1year