Skip to main content

Notification Recipient Storage

Summary

NotificationRecipients is a directory of resolved recipients, upserted from event payloads and keyed uniquely by tenant, recipient type and external reference.

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. Written by the dispatcher's upsert routine during event processing and by the template test endpoint's live-send path; read by the inbox endpoint and by the manual retry path.

Columns

ColumnTypeConstraint
IdGUIDPrimary key
TenantIdstringMax length 80, nullable
RecipientTypestringMax length 40, required
ExternalRefstringMax length 200, required
DisplayNamestringMax length 300, nullable
EmailstringMax length 320, nullable
PhonestringMax length 40, nullable
CreatedOnUtctimestamp
UpdatedOnUtctimestamp

Recipient type values are stored as plain strings drawn from a constants class: user, employee, role and address.

Indexes

IndexColumnsUnique
UX_Recipients_Tenant_Type_Reftenant, type, external referenceYes

Upsert and merge

The dispatcher queries for an existing row on the exact unique-key triple. When absent it inserts a new row; when present it merges, preferring freshly supplied non-empty contact values over stored ones and leaving stored values intact when the incoming value is blank. The updated timestamp is always refreshed. The merge is a coalesce — supplying a blank value cannot clear an existing address.

Race window. The lookup and the insert are separate operations with no transaction, no locking hint and no upsert statement. Two concurrent events for the same unseen recipient can both find nothing and both attempt an insert; the unique index is what ultimately rejects the loser, and no reviewed code path catches that violation. See concurrency.

Address resolution

The concrete delivery address written onto a message is selected from the recipient by channel: the external reference for in-app, the email address for email, and the phone number for both SMS and WhatsApp. A recipient with no address for the requested channel causes that channel to be skipped and audited rather than failing.

Lifecycle

Rows are inserted or merged and never deleted by any reviewed code path. The template test live-send path inserts an address-type recipient on every invocation without consulting the unique key first.

Classification

Implemented.

Requires confirmation

Whether the test-send path should reuse rather than insert address-type recipients requires confirmation.

See Also

Keywords

  • Notification database
  • Recipient 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

Revision Information

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