Skip to main content

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

ColumnTypeConstraint
IdGUIDPrimary key, defaulted in the entity
TenantIdstringMax length 80, nullable
EventIdGUIDNullable — null for API-originated test sends
EventTypestringMax length 160, nullable
CorrelationIdstringMax length 160, nullable
TemplateKeystringMax length 160, required
TemplateIdGUIDNullable
TemplateVersionint
ChannelstringMax length 40, required
RecipientIdGUIDPlain value, no constraint
ToAddressstringMax length 320
SubjectstringMax length 1000
BodytextExplicit column type
StatusstringMax length 40, required
Attemptsint
LastErrorstringMax length 2000, nullable
CreatedOnUtctimestamp
SentOnUtctimestampNullable
ReadOnUtctimestampNullable

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

IndexColumnsUnique
IX_Messages_Recipient_Statusrecipient, statusNo
IX_Messages_Tenant_Channeltenant, channelNo
IX_Messages_EventIdeventNo

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.

See Also

Keywords

  • Notification database
  • Notification Storage
  • Draft database documentation

Source References

  • microservices/src/notification-service/Infrastructure/NotificationDbContext.cs
  • microservices/src/notification-service/Domain/NotificationEntities.cs
  • microservices/src/notification-service/Application/NotificationDispatcher.cs
  • microservices/src/notification-service/Api/NotificationEndpoints.cs
  • microservices/src/notification-service/Infrastructure/Migrations/20260705113059_InitialNotificationSchema.cs

Revision Information

  • Status: Draft
  • Last reviewed: 2026-07-21
  • Review cycle: Quarterly