18. Troubleshooting
18.1 "A user isn't receiving notifications at all"
Debug checklist, in order:
- Is there a
NotificationEventConfigurationrow for the event'sEventKey, and is the relevant channel'sEnable{Channel}flagtrue? No row at all means all channels are enabled by default — a row that exists but has the flag off is the more common cause. See §9.4. - Does an
AppNotificationTemplaterow exist for thatEventKey+Channel+ the recipient's language (or aLanguageCode = nullfallback)? Missing template = silent skip, no error. See Chapter 8. - Check
NotificationDeliveryAttemptfor theEventKey/recipient — was an attempt even made? If nothing appears, the dispatch call itself may not be happening (a bug in the calling business service) rather than a delivery failure. - If an attempt exists with
Succeeded = false, readErrorCode/ErrorMessage— this usually points straight at the root cause (bad recipient, provider auth failure, etc.). - For Push specifically: does the recipient have an active
DeviceTokenrow (GetUserDevices/{userId}, §9.3)? No device tokens means Push is silently a no-op for that user. - For SignalR specifically: was the recipient connected at dispatch time? SignalR has no redelivery — check
the InApp channel /
GetInboxinstead for durable confirmation.
18.2 "WhatsApp messages aren't sending"
Walk the guard chain in §7.3 top to bottom — each guard logs a
specific LogInformation/LogWarning line naming exactly which condition failed:
EnableWhatsApp/EnableWhatsAppNotificationsfalse → "WhatsApp notification dispatch skipped: disabled"EnableWhatsAppTemplatesfalse → "WhatsApp template dispatch skipped: EnableWhatsAppTemplates=false"- Missing
WhatsAppTemplateNameon the template → "WhatsApp dispatch skipped: template has no WhatsAppTemplateName" - Missing
AccessToken/PhoneNumberIdin configuration → "WhatsApp dispatch skipped: missing AccessToken or PhoneNumberId" - Phone normalization failure → "WhatsApp dispatch skipped: invalid phone"
Search application logs for these exact phrases (they include EventKey in the log context) to find the
precise guard that tripped, rather than guessing from the outside.
18.3 "WhatsApp delivery receipts never update"
Confirm WhatsAppCloudApi.SignatureValidationEnabled is true and that AppSecret matches what's registered
in the Meta app — a signature mismatch causes the inbound webhook to reject the callback silently from the
caller's perspective (Meta sees a failure and will retry/eventually stop). Also confirm the receipt-mapping
call (INotificationDeliveryReceiptService.UpsertAsync) isn't throwing — it's wrapped in a try/catch so a
mapping bug won't break the pre-existing ProcessWebhookStatusAsync logic, but it also means a silent
receipt-mapping failure won't surface as an error anywhere obvious; check logs for the receipt-mapper's
warning output specifically.
18.4 "A notification is stuck in the retry queue and never resolves"
Check NotificationRetryQueue.ProcessingToken — if non-null and State = Retrying for more than 2 hours,
it should be picked up by the retry-expire job's orphan repair (hourly). If it's older than that and still
stuck, the notification-retry-expire-pipeline job itself may not be running — check the Hangfire dashboard
for its last execution time (see §16.3).
18.5 "Analytics numbers look wrong / don't match expectations"
Confirm the date range being queried — most analytics endpoints default to last 7 days and cap at
90 days (§9.8) if no explicit dateFrom/dateTo is
passed; a query intended to cover a longer historical window will silently be truncated to 90 days.
18.6 "The server checklist's ResultState numbers don't match what I see in the database"
This is a known documentation bug in docs/NOTIFICATION_FRAMEWORK_PHASE441_SERVER_CHECKLIST.md, corrected in
this guide — use the actual enum values from §12.1
(Succeeded=2, Failed=5, Retrying=4, Expired=8), not the older checklist's 1/2/3/4 mapping.
18.7 Debug checklist template
[ ] Confirm NotificationEventConfiguration allows the channel for this EventKey
[ ] Confirm a matching AppNotificationTemplate exists (correct EventKey + Channel + Language, or a null-language fallback)
[ ] Confirm a NotificationDeliveryAttempt row exists for this dispatch
[ ] Read ErrorCode/ErrorMessage on any failed attempt
[ ] For Push: confirm an active DeviceToken exists for the recipient
[ ] For SignalR: confirm the recipient was connected at dispatch time; check GetInbox as the durable fallback
[ ] For WhatsApp: walk the 5-step guard chain in §7.3 against the application logs
[ ] Check NotificationRetryQueue / NotificationDeadLetter for the same CorrelationId/EventKey
[ ] Check Hangfire dashboard for the relevant pipeline job's last successful run
18.8 Using SendTestEmail safely
NotificationHistoryController.SendTestEmail (§9.12)
has no permission gate — restrict its use to lower environments (local/staging) with a test mailbox, never
call it against a production recipient you don't control, and treat any production usage of it as a finding
to raise with the platform team, not a supported workflow.
18.9 Logs to check
- Application logs: search for the
EventKeyand/orCorrelationIdassociated with the notification in question — every guard/failure log line in the dispatch and channel-provider code includes at least one of these. - Hangfire dashboard: confirm the relevant pipeline job (
notification-retry-processor-pipeline,notification-deadletter-cleanup-pipeline,campaign-*-pipeline) is listed as a recurring job and has recent successful executions, not just registered. NotificationDeliveryReceipttable: for WhatsApp specifically, cross-referenceProviderMessageIdagainst what your Meta Business dashboard reports for the same message.