Notification Idempotency Storage
Summary
ProcessedIntegrationEvents is the idempotency ledger — one row per consumed integration event, unique on the event identifier. The dispatcher consults it before doing any work so a redelivered event is skipped.
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 mark-processed helper at the end of processing. Read by the dispatcher's duplicate check and by the processed-event lookup endpoint.
Columns
| Column | Type | Constraint |
|---|---|---|
Id | GUID | Primary key |
EventId | GUID | Uniquely indexed |
EventType | string | Max length 160, required |
TenantId | string | Max length 80, nullable |
CorrelationId | string | Max length 160, nullable |
SourceService | string | Max length 120 |
Outcome | string | Max length 40, required |
MessagesCreated | int | — |
ProcessedOnUtc | timestamp | — |
Indexes
| Index | Columns | Unique |
|---|---|---|
UX_ProcessedEvents_EventId | event | Yes |
IX_ProcessedEvents_Correlation | correlation | No |
The unique index on the event identifier is the structural backbone of duplicate suppression. Note the surrogate primary key is a separate column, so the event identifier's uniqueness is enforced by the index rather than by the key.
Outcomes
Seven outcome values are defined as constants and stored as plain strings: processed, duplicate, stale, no template, no recipient, suppressed and failed. The stored outcome also carries the count of messages created for that event.
Check-then-write race window
Duplicate suppression is a read followed much later by a write. The dispatcher performs a no-tracking existence check at the start of processing, then does all its work, and only writes the ledger row at the end.
Two concurrent deliveries of the same event can therefore both pass the existence check and both process the event fully, duplicating messages and delivery. The unique index rejects only the second ledger insert — after the duplicate side effects have already occurred — and no reviewed code path catches that violation. See concurrency.
Duplicate path
When the check does find a row, the dispatcher writes an audit row at the duplicate stage and returns without writing a second ledger row, so the ledger keeps exactly one row per event while the audit trail records every redelivery observed.
Lifecycle
Insert-only. No reviewed code path updates or deletes a ledger row, so the ledger grows without bound and duplicate suppression never expires.
Classification
Implemented.
Requires confirmation
Whether the check-then-write window is accepted, and whether ledger retention is planned, require confirmation.
Related Articles
See Also
Keywords
- Notification database
- Idempotency 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