HOW TO TEST
How to Test Notifications.
Core Features A complete testing guide for notification systems: trigger conditions, email/SMS/push/in-app delivery, content accuracy, personalisation, deep links, timing, retry logic, duplicate prevention, user preferences, unsubscribe, locale handling, and failure auditing.
Notifications are the application's primary channel for reaching users after they leave the UI. A bug here silently fails the user: a missed order confirmation, a duplicate alert flooding an inbox, a deep link navigating to the wrong screen, or a unsubscribe link that does not work. This guide covers the full notification surface: trigger conditions for each notification type, multi-channel delivery (email, SMS, push, in-app), content and personalisation accuracy, deep links, timing and scheduling, retry and idempotency, duplicate prevention, user preference enforcement, unsubscribe and opt-out, locale and timezone handling, failure modes, and audit trails. All test cases are written for a Cypress/Playwright engineer with access to a test inbox API.
Risks
Notification sent despite user opting out
If preference or unsubscribe state is checked at creation time rather than delivery time — or if the check is applied to only one channel — a user who has opted out can still receive notifications via another channel.
Duplicate notifications from retry without idempotency
If a delivery failure triggers a retry and the notification system does not track which deliveries have already succeeded, users receive multiple copies of the same notification on retry.
Deep links navigating to the wrong content
A push notification's deep link pointing to entity ID 'order-123' opens the correct screen in development but navigates to a different entity or a 404 in production due to environment-specific ID schemes or missing route handlers.
Notification content exposes another user's data
Template rendering bugs where the recipient's token is not correctly scoped can include another user's name, order details, or account information in the notification — a serious privacy breach.
Time-sensitive notifications arriving late or not at all
Password-reset links, two-factor codes, and order-update notifications have short validity windows. Delivery delays caused by queue backlogs, rate limiting by the email provider, or webhook failures render them useless.
Failed notifications silently dropped — no retry or alert
If the notification service does not implement retry logic or alerting on delivery failure, critical messages (order confirmations, security alerts) are silently lost with no visibility to the team or the user.
Unsubscribe links broken or bypassed
An unsubscribe link that returns a 404, a generic error, or that does not update the user's preference record leaves users unable to opt out and exposes the product to legal liability under CAN-SPAM and GDPR.
Test Scenarios
Correct trigger conditions fire the notification
CriticalfunctionalFully automatedPerform the triggering action (e.g. place an order, reset a password, share a file) and assert the notification is sent. Assert no notification fires for non-trigger actions.
Email notification is delivered to the correct recipient
CriticalfunctionalFully automatedTrigger a notification and poll the test inbox. Assert the email arrives within the defined SLA (e.g. 60 s), is addressed to the triggering user's email only, and not to any other address.
Notification content matches the trigger context
CriticalfunctionalFully automatedAssert the notification body contains the correct personalised data: user name, order ID, amount, entity name — not placeholder text, wrong user data, or missing fields.
Deep link in notification navigates to the correct entity
HighfunctionalFully automatedClick the CTA link in the notification. Assert it opens the correct screen/page with the correct entity (e.g. order detail for the specific order that triggered the notification, not a generic page).
One trigger produces exactly one notification per channel
CriticalfunctionalFully automatedPerform a triggering action once. Assert exactly one email, one push, and one in-app notification is created. Assert retrying the API trigger (with the same idempotency key) does not create a second notification.
Failed delivery is retried and eventually succeeds
HighnegativeFully automatedSimulate a transient delivery failure (mock the email/SMS provider to return 503 on the first attempt). Assert the notification system retries within the documented backoff window and delivers on retry.
Notification is not sent when the user has disabled that type
CriticalfunctionalFully automatedDisable a notification type in user preferences (e.g. marketing emails off). Trigger the notification. Assert the notification is NOT delivered via that channel. Assert other channels are unaffected if only one was disabled.
Unsubscribe link correctly opts the user out
CriticalfunctionalFully automatedClick the unsubscribe link in an email. Assert the page confirms the opt-out and the user's preference is updated in the database. Assert no further emails of that type are sent after opt-out.
Notification is sent in the user's configured language
HighfunctionalFully automatedSet a user's locale to French (fr). Trigger a notification. Assert the email subject, body, and CTA button are in French. Assert no English text appears in localised sections.
Scheduled notification is sent at the correct time in the user's timezone
HighfunctionalFully automatedFor a notification scheduled at a specific time (e.g. daily digest at 9 AM), set the user's timezone to UTC+5:30. Assert the notification is delivered at 9 AM IST, not 9 AM UTC.
Failed notification delivery is recorded in the audit log
HighfunctionalFully automatedForce a permanent delivery failure (invalid email address, disabled push token). Assert the failure is recorded in the notification log with: timestamp, channel, error code, and recipient.
In-app notification appears in real time without page refresh
HighfunctionalFully automatedWhile a user is authenticated and the app is open, trigger a notification from another session or API. Assert the in-app notification badge/count updates and the notification appears in the bell menu without requiring a page refresh.
Detailed Test Cases
Preconditions
- Test user with email test-notif@example.com
- Test inbox API accessible (Mailhog, Mailtrap)
- Order placement API available
Steps
- 1.POST /api/orders to create a new order for the test user
- 2.Record the returned orderId
- 3.Poll test inbox for an email to test-notif@example.com (timeout 60 s)
- 4.Assert email arrives within 60 s
- 5.Assert email subject contains the order reference
- 6.Assert email body contains: user's first name, orderId, item list, total amount
- 7.Assert email body does NOT contain placeholder tokens like {{firstName}} or [ORDER_ID]
- 8.Assert the CTA link leads to /orders/{orderId} (not /orders/{someOtherId})
Expected result
Email arrives within 60 s with correct personalised content and a working order deep link.
Test data
- Recipient: test-notif@example.com
- Order ID: from POST /api/orders response
Edge Cases
Notification triggered for an account that was deleted between trigger and delivery
If a user deletes their account after an event fires but before the notification is delivered, the system should silently discard the notification and not log an error about a missing user.
User changes their email address between trigger and delivery
If an email notification is queued and the user updates their email while it is in the queue, the delivery should go to the new email address, not the old one — or use a snapshot of the email at enqueue time (document which behaviour is intended).
Notification triggered millions of times by a runaway job
A background job bug causes the same notification event to fire 10,000 times in a minute. The system should have rate limiting per user+event-type to prevent inbox flooding even if the upstream trigger misfires.
In-app notification count badge not updated after read
The unread-notification badge should decrement when the user opens the notification. If it stays at the stale count, users ignore the badge as unreliable.
Notification sent to a push token that has been invalidated
Push tokens expire when a user reinstalls the app, changes devices, or revokes permission. The push provider returns a 'token not registered' error that the system must handle by removing the stale token from the database.
Notification with a time-expired deep link
A push notification linking to a time-limited promotion arrives after the promotion has ended. The link should navigate to a graceful 'offer expired' page, not a 404.
Notification preferences set to off but a transactional email is still sent
Marketing preferences should not suppress transactional notifications (receipts, password resets). The preference system must distinguish between marketing and transactional channels.
Timezone stored as a name rather than an offset — DST transition
Storing 'America/New_York' (correct) vs '-05:00' (incorrect — doesn't handle DST). Scheduled notifications should use IANA timezone names and be re-evaluated at send time to correctly account for daylight saving time changes.
Long notification body exceeds SMS character limit
SMS messages over 160 characters are split into multiple parts. If the application does not account for this, the message body is truncated at exactly 160 characters mid-sentence.
Automation Ideas
Test inbox polling for email delivery assertions
Integrate Mailhog or Mailtrap's REST API into the test suite. After triggering a notification, poll the inbox with a timeout and assert the email content — subject, personalisation, deep link — without manual checking.
Tools: playwright, cypress
Mock notification provider for failure and retry tests
Use Mock Service Worker (MSW) or Wiremock to stub the email/SMS provider. Configure it to return 503 on the first call and 200 on the second. Assert the notification log records the retry and the eventual success.
Tools: msw, wiremock, playwright
Idempotency key collision test
Fire the same trigger twice with the same idempotency key via the API. Assert the inbox contains exactly one email. Automatable via parallel API calls + inbox count assertion.
Tools: playwright, postman
Notification preference enforcement matrix
Drive a matrix of {channel, preferenceState, triggerType, expectedDelivery} through the notification trigger endpoint. Assert each combination: opt-out + marketing → 0 emails, opt-in + transactional → 1 email, etc.
Tools: playwright, postman
Localisation completeness check
For each supported locale, trigger all notification types and assert: (1) no {{placeholder}} tokens in the rendered output, (2) subject is non-English for non-English locales, (3) character encoding is correct (no mojibake for CJK or accented characters).
Tools: playwright, cypress
Audit log completeness assertion
After each notification delivery (success or failure), assert the audit log entry contains: notificationId, userId, channel, status, timestamp, and errorCode if failed. Run as a contract test against the audit API.
Tools: playwright, postman
Common Bugs
Email sent to the previous email address after an address change
The notification job captures the user's email at enqueue time and caches it. When the user updates their email before delivery, the job still uses the cached address, which may belong to someone else.
Impact: Sensitive notifications sent to a third party; data leak if the old email is now owned by another person.
Unsubscribe link uses GET and is triggered by email preview
Some email security tools and clients pre-fetch all links in an email to check for malware. An unsubscribe link that opts out on GET (not POST) causes users to be unsubscribed without their knowledge when the email is scanned.
Double delivery on transient webhook failure
When the notification webhook to the email provider times out, the notification service retries — but the first request had already been accepted and queued by the provider. The provider delivers both, and the user receives two identical emails.
In-app notification count does not decrement on read
The unread count is fetched on page load but never updated in real time. After reading all notifications, the badge still shows the old count until the page is refreshed.
Placeholder tokens appear in production emails
Template rendering fails silently when a data field is null. Instead of falling back to a default, the raw template token ({{firstName}}, {{orderTotal}}) is included in the sent email.
Notification preferences UI shows 'off' but backend sends anyway
The preference toggle is a UI-only control that updates a local state variable but fails to POST the change to the API due to a missing error handler. The backend still has the old preference state.
Notification fired for cancelled or rolled-back orders
A notification event is emitted synchronously when the order is created, before the payment confirmation arrives. If payment fails and the order is cancelled, the 'order confirmed' email has already been sent.
SMS truncated at exactly 160 characters mid-word
Long SMS notification bodies are not split intelligently. The application truncates at 160 characters without word boundary detection, breaking messages mid-word.
Useful Tools
End-to-end notification flows, inbox polling via API, and in-app notification UI assertions.
Notification E2E tests with cy.intercept for stubbing webhook endpoints and testing retry behaviour.
Stub email/SMS provider APIs to simulate delivery failures, delays, and rate limits deterministically.
Intercept notification provider API calls at the network layer in browser tests to simulate failures.
Trigger notification events via API, assert audit logs, and test idempotency key behaviour.
Catch test emails, inspect content and headers, and query deliveries via REST API without real SMTP.
Mock third-party notification providers (email, SMS, push) locally with configurable response scenarios.
// Related resources