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
| Column | Type | Constraint |
|---|---|---|
Id | GUID | Primary key |
TenantId | string | Max length 80, nullable |
RecipientType | string | Max length 40, required |
ExternalRef | string | Max length 200, required |
DisplayName | string | Max length 300, nullable |
Email | string | Max length 320, nullable |
Phone | string | Max length 40, nullable |
CreatedOnUtc | timestamp | — |
UpdatedOnUtc | timestamp | — |
Recipient type values are stored as plain strings drawn from a constants class: user, employee, role and address.
Indexes
| Index | Columns | Unique |
|---|---|---|
UX_Recipients_Tenant_Type_Ref | tenant, type, external reference | Yes |
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.
Related Articles
See Also
Keywords
- Notification database
- Recipient 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.cs
Revision Information
- Status: Draft
- Last reviewed: 2026-07-21
- Review cycle: Quarterly