Notification Message Storage
Summary
NotificationMessages is the central table. One row is a single rendered notification bound to exactly one channel and one recipient. For the in-app channel the row is the inbox item; for the other channels it is the outbound record.
Audience
Engineers, architects, QA, support and security reviewers.
Reference Content
The verified reference material for this topic is set out in the sections below.
Ownership
Owned by NotificationDbContext and written by the dispatcher during event processing, by the template test endpoint on its live-send path, and by the read-state endpoint.
Columns
| Column | Type | Constraint |
|---|---|---|
Id | GUID | Primary key, defaulted in the entity |
TenantId | string | Max length 80, nullable |
EventId | GUID | Nullable — null for API-originated test sends |
EventType | string | Max length 160, nullable |
CorrelationId | string | Max length 160, nullable |
TemplateKey | string | Max length 160, required |
TemplateId | GUID | Nullable |
TemplateVersion | int | — |
Channel | string | Max length 40, required |
RecipientId | GUID | Plain value, no constraint |
ToAddress | string | Max length 320 |
Subject | string | Max length 1000 |
Body | text | Explicit column type |
Status | string | Max length 40, required |
Attempts | int | — |
LastError | string | Max length 2000, nullable |
CreatedOnUtc | timestamp | — |
SentOnUtc | timestamp | Nullable |
ReadOnUtc | timestamp | Nullable |
Relationships
None declared. RecipientId and TemplateId are plain GUID columns with no navigation property and no foreign key. The template reference additionally records TemplateKey and TemplateVersion as denormalised values, so the rendered content's provenance survives even though nothing enforces that the referenced template still exists.
Tenant fields
TenantId is nullable and carries the tenant supplied on the source event. No query filter applies it; see tenant isolation.
Indexes
| Index | Columns | Unique |
|---|---|---|
IX_Messages_Recipient_Status | recipient, status | No |
IX_Messages_Tenant_Channel | tenant, channel | No |
IX_Messages_EventId | event | No |
The inbox query filters on recipient identifiers and channel and orders by creation time descending; the composite recipient-and-status index covers the recipient and unread predicates but not the ordering column. The failed-queue query filters on status alone, which no index leads with.
Lifecycle
Status values are stored as plain strings drawn from a constants class: pending, sent, failed, read and skipped. A row is inserted in the pending state, moves to sent on a successful delivery attempt with the sent timestamp stamped, or to failed once attempts are exhausted with the error recorded. In-app rows may later move to read with the read timestamp stamped. The manual retry path resets a row to pending and clears the last error before re-delivering.
Rows are never deleted by any reviewed code path.
Classification
Implemented.
Requires confirmation
Whether a covering index for the inbox ordering and the failed-queue scan is intended requires confirmation.
Related Articles
See Also
Keywords
- Notification database
- Notification Storage
- Draft database documentation
Source References
microservices/src/notification-service/Infrastructure/NotificationDbContext.csmicroservices/src/notification-service/Domain/NotificationEntities.csmicroservices/src/notification-service/Application/NotificationDispatcher.csmicroservices/src/notification-service/Api/NotificationEndpoints.csmicroservices/src/notification-service/Infrastructure/Migrations/20260705113059_InitialNotificationSchema.cs
Revision Information
- Status: Draft
- Last reviewed: 2026-07-21
- Review cycle: Quarterly