Skip to main content

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

ColumnTypeConstraint
IdGUIDPrimary key
EventIdGUIDUniquely indexed
EventTypestringMax length 160, required
TenantIdstringMax length 80, nullable
CorrelationIdstringMax length 160, nullable
SourceServicestringMax length 120
OutcomestringMax length 40, required
MessagesCreatedint
ProcessedOnUtctimestamp

Indexes

IndexColumnsUnique
UX_ProcessedEvents_EventIdeventYes
IX_ProcessedEvents_CorrelationcorrelationNo

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.

See Also

Keywords

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