Skip to main content
Version: 1.2

9.6 Notification Retry Queue

Controller: NotificationRetryQueueController · Route: api/v1/NotificationRetryQueue · GroupName: notifications · 4 endpoints · Read-only visibility plus two operator actions over the system-managed NotificationRetryQueue table — there is no Create/Update, since rows are only ever created by the dispatch/retry engine itself. Full engine detail in Chapter 12.

Verb + routePermissionRequestResponse (data)
POST GetDataTableNotificationRetryQueues.ViewAllDtParametersDtResult<NotificationRetryQueueDto> (unwrapped)
GET GetDetails/{id}NotificationRetryQueues.ViewNotificationRetryQueueDetailsDto
PUT RetryNowNotificationRetryQueues.RetryNowDeleteDto[]Guid
PUT CancelNotificationRetryQueues.CancelDeleteDto[]Guid

GET GetDetails/{id} — response shape

{
"succeeded": true,
"message": null,
"data": {
"id": "3e4f5a60-0000-0000-0000-000000000001",
"originalNotificationId": "8b1e2f40-0000-0000-0000-000000000001",
"channel": 1,
"recipient": "customer@example.com",
"eventKey": "PurchaseInvoicePosted",
"correlationId": "corr-9f1a2b3c",
"attemptNumber": 2,
"maxRetries": 5,
"state": 3,
"scheduledAt": "2026-07-04T10:15:00Z",
"processedAt": null,
"lastErrorCode": "SMTP_TIMEOUT",
"lastErrorMessage": "The SMTP server did not respond within the configured timeout.",
"retryStrategy": 2,
"nextDelaySeconds": 120,
"createdOn": "2026-07-04T10:11:00Z"
}
}

state (NotificationDeliveryState): Pending=0, Processing=1, Succeeded=2, RetryScheduled=3, Retrying=4, Failed=5, DeadLetter=6, Cancelled=7, Expired=8.

PUT RetryNow — force an immediate retry attempt

Request:

[ { "id": "3e4f5a60-0000-0000-0000-000000000001" } ]

Response:

{ "succeeded": true, "message": "Notification retry queue entry requeued for immediate processing", "data": "3e4f5a60-0000-0000-0000-000000000001" }

Business notes: sets ScheduledAt to now (or clears the claim) so the next notification-retry-processor-pipeline Hangfire run (every 2 minutes) picks it up immediately instead of waiting for its computed backoff delay. It does not bypass the retry engine's atomic claiming (ProcessingToken) — a row already being processed by a concurrent worker run is not affected.

PUT Cancel

Request: same DeleteDto[] shape. Sets State = Cancelled — the row is retained for audit history (this table uses soft-delete conventions but a cancelled retry is not the same as a deleted row) and will never be picked up by the retry processor again, nor will it be escalated to a dead letter.

Angular: an operator "retry queue" dashboard — typically shows Pending/RetryScheduled/Retrying rows with a countdown to ScheduledAt, and per-row "Retry Now"/"Cancel" buttons. Backend: NotificationRetryProcessorJobAdapterINotificationRetryWorker.ProcessDueRetriesAsync, run by the Background Jobs Framework pipeline every 2 minutes — see Chapter 12. Related APIs: Delivery Policies (retry rules), Dead Letters (where exhausted retries land), Analytics → RetryQueue (aggregate metrics).