InfoWok

Design a Notification Service: Failure Modes, Security and Follow-Ups (2026)

The working notification design is half the round. The other half is production: what breaks and what already handles it, two regions with one sender per user, who may publish an event, opt-out and send-hour rules, and the follow-ups interviewers ask.

SK
Sukhveer Kaur
Published October 24, 2026
7 min read
Open in ChatGPT Open in Claude
Notification service follow-ups: a failure table, two regions with a home region per user, and caller checks around the decide, store, nudge pipelineSoftware Architecture
NOTIFICATION SERVICE FOLLOW-UPS
On this page +

A team ships the notification service from the previous lesson. It decides once, stores first and nudges with an id. For a month it works.

Then a region goes dark at 2 a.m., one second after a login code left. A marketing text lands in a customer’s bedroom at 6 a.m. A security review asks who is allowed to send “password reset”. An interviewer, having heard the happy path, says the sentence that fills the last twenty minutes: “Now what breaks?”

A design is not finished until you can say how it fails, who may call it, and what you watch.

Intermediate30 min
Before you start
Remember from the previous lesson

Why can a worker retry a job it is not sure it finished? The job carries only an id, the worker loads the stored record, and a duplicate check on that id stops a second alert.

Key takeaways
  • Walk a failure table component by component: what dies, what users see, and the answer already designed in.
  • Give each user one home region and declare failovers, so a user never has two senders; a rare repeat is cheaper than strong cross-region consistency.
  • Authenticate every caller and authorize by event type, so one service cannot send another service’s messages.
  • Treat opt-out and send hours as design inputs: separate transactional and marketing types, a suppression list, a time zone per user.
  • Watch the age of the oldest urgent job: it shows a late login code before a user does.
After this lesson you can

Defend a working notification design in the second half of a system design round: name what fails and which part answers it, place the service in two regions, restrict who may publish, apply opt-out and send-hour rules, and answer the usual follow-ups within a 45-minute plan.

Predict first. If the decide step is down, should a login code still go out? Decide before you read the first row of the table below.

What Breaks, and What Do You Watch?#

If this diesWhat happensAnswer
Decide stepNo new notificationsProducers retry with the same key. Choose the default: fail open for codes, closed for marketing
QueueAccepted events stallThe inbox keeps the record; a sweep re-queues stuck “pending” rows
A workerThe job is delivered againThe duplicate check by id makes it safe
A providerErrors and timeoutsBackoff, expiry, dead-letter queue; codes fall back to SMS
Inbox storeCannot saveDo not send; return an error so the producer retries
A regionSee the regions section belowDeclared failover, a short TTL, a collapse id

Watch the age of the oldest job in each lane, provider errors by status code, dead-letter depth, and token removals per hour. Assume a goal of handing 99% of login codes to a provider within 5 seconds (a made-up target), and alert when the oldest urgent job nears that. Such a target is called an SLO.

The oldest urgent job tells you a login code is late before a user does. Provider callbacks show delivery; open tracking needs a client event and is out of scope.

Cost and rollout (design judgement): SMS is billed per message while push is not, so fall back to SMS only for codes. Migrate one event type at a time behind a flag, run the new path in shadow (decide and store, do not push), compare it with the old one, and copy preferences over before the cutover.

Where Does It Run, and Who May Call It?#

Concept in use: Sharding and replication

The inbox is partitioned by user and copied to a second region, and the copy lags.

Works · One region, restore elsewherecorrect, simplest

Run in one region, with backups restorable in another.

Trade-offA region outage stops login codes until the restore ends.
Scales · Active in two regions, one home region per userhandles the load

Route each event to the user’s home region. Only that region decides, stores and sends for the user, and its inbox is copied to the other region. Both regions call the same providers.

Trade-offCopies are asynchronous. DynamoDB global tables, for example, replicate in their default mode typically within a second or less and settle concurrent writes by “last writer wins”, so two regions sending for one user would race.
Survives · Declared failover, one sender at a timehandles failure and growth

Region B takes over A’s users only after a failover is declared, not on a network blip, so a user never has two senders. A job A sent just before failing can go out again from B, because its “sent” status had not arrived. A short TTL and a collapse id, which APNs uses to merge notifications, turn that repeat into a replacement, not a second alert.

Trade-offStrong cross-region consistency would close the gap at a latency cost per write. DynamoDB’s strongly consistent mode, for example, needs exactly three regions, adds a cross-region round trip per write, and does not support TTL, which the inbox below relies on. For notifications, a rare repeat is usually cheaper. In CAP and PACELC terms, this design picks availability during a partition and low latency otherwise, and pays for consistency in one place: opt-outs.

Which Store Holds the Inbox?#

The inbox is written once, read as one user’s newest-first list, and deleted when it ages out, which suits a key-value or wide-column store keyed by user and sorted by time (see SQL vs NoSQL). DynamoDB’s per-item TTL deletes expired items typically within a few days, so retention is cheap but not exact, and workers must still check expires_at.

Preferences are small and read consistently, so keep them in a relational database.

Who May Publish an Event?#

POST /events is internal. A caller authenticates with an OAuth 2.0 client credentials token, or mutual TLS (RFC 8705). Then authorize by event type: the order service may send “order shipped”, not “password reset”. Give each caller a token-bucket quota and answer 429 past it, so one buggy service cannot fill the urgent lane.

Some rules come from law, not product taste. This is design guidance, not legal advice; check the rule for your market with counsel.

RuleWhat it asksWhat it forces in the design
US TCPA hoursNo telephone solicitations before 8 a.m. or after 9 p.m. at the called party’s location (47 CFR 64.1200(c)(1))Store a time zone per user; the scheduler holds marketing SMS until the window opens
CAN-SPAMStop commercial email within 10 business days of an opt-out request (15 U.S.C. 7704(a)(4))A suppression list the decide step reads; apply opt-outs at once
GDPR Article 21After an objection, personal data is no longer processed for direct marketingAn opt-out per purpose, applied to campaign lists too
Gmail bulk sendersOver 5,000 messages a day to Gmail: one-click unsubscribe, spam rate under 0.30%Marketing email carries unsubscribe headers; keep it on a separate stream from codes

Keep transactional and marketing as separate types with separate opt-outs, so turning off promotions never blocks a login code, and log every opt-in and opt-out.

Follow-Ups the Interviewer Will Ask#

In an interview

Expect these follow-ups after the main design; back each answer with a trade-off and a number. The 10x row builds on the previous lesson’s assumed peak of 11,600 events a second.

Follow-upStrong short answer
10x traffic (≈ 116,000 events a second at peak)Add queue partitions and workers. The binding limit is the provider: FCM’s default is 10,000 a second, and an increase goes through Firebase Support, requested at least 15 days ahead
One event, 10 million recipientsDo not fan out in the request. A fan-out job splits users into ranges and feeds the bulk lane
A provider is downRetries and expiry, a dead-letter queue, SMS fallback for codes only
Exactly once?No. At-least-once, an id check, and a collapse id
Millions of timed sendsBucket by send-at minute; a scheduler per partition queues each bucket when due
A user has five devicesOne job and one id per device; the inbox holds one record
LanguagesTemplate id plus locale and version; fall back to a default language

A 45-minute plan: 5 for requirements and assumptions, 5 for numbers and API, 10 for the inline design and queue, 20 for deep dives, 5 for failures and wrap-up.

Learning tip

Cover the right-hand column of the table and say each answer aloud in one breath. Do it again tomorrow without the cover.

The 45-Minute Plan#

Spend the round in this order: 5 minutes for requirements and assumptions, 5 for numbers and API, 10 for the inline design and queue, 20 for deep dives, and 5 for failures and wrap-up. The failure table and the follow-ups above are what the last 5 minutes, and any spare deep-dive time, are for.

Try It: A Marketing Text at 7:30 p.m. Pacific#

Exercise · A promotional text, two time zones10 min

A campaign job for a marketing SMS reaches the scheduler at 7:30 p.m. Pacific. The user’s stored time zone is New York (Eastern, 3 hours ahead). Assume this text counts as a telephone solicitation under the 8 a.m. to 9 p.m. rule in the table above.

  1. What is the local time for the user, and may the text go now?
  2. What should the scheduler do?
  3. The same user requests a login code at the same moment. What happens to it?
Hint

Convert to the user’s clock first. Then ask which message types the send-hour rule applies to.

One reasonable answer
text
7:30 p.m. Pacific
  + 3 hours
  = 10:30 p.m. Eastern

That is after 9 p.m., so hold the text. Save it with a send-at time of 8 a.m. Eastern the next day and let the scheduler queue it then.

The login code is a transactional type, so the marketing hold does not apply and it goes out at once through the urgent lane.

Check Yourself#

Check yourself3 questions · answer, then open
1.Why does the failure table say 'do not send' when the inbox store cannot save?

Without a saved record there is nothing to retry from and nothing to check a duplicate against. Return an error so the producer retries with the same key.

2.What would change if region A failed one second after sending a login code?

Region B takes over A’s users once failover is declared, but the “sent” status may not have been copied yet, so B can send the code again. A short TTL and a collapse id make the repeat replace the first alert.

3.The order service publishes the event type 'password reset'. What stops it?

Authorization by event type. Authentication proves who the caller is; the allow-list per caller decides what it may send. The rejected call also counts against that caller’s quota.

In One Picture#

text
Failures
  each row: what dies, the answer
Regions
  one home region per user,
  declared failover
Callers
  authenticate, authorize by
  event type, then quota
Rules
  opt-out and send hours feed
  the decide step
Watch
  age of the oldest urgent job

Conclusion#

The working design carries the first half of the round. This half decides whether the interviewer believes you could run it.

Which row of the failure table would you check first on your own system? Tell us in the comments.

Where to go from here

Frequently asked questions

What should happen when a whole region fails? +
Declare a failover, then let the other region take over that region's users, so each user has one sender at a time. A job sent just before the failure can go out again, because its sent status had not been copied across yet. Send with a short expiry and a collapse id, which Apple's APNs documentation describes as the way to merge notifications, so the repeat replaces the first alert.
Which metric shows first that a notification service is unhealthy? +
The age of the oldest job in the urgent lane. If login codes should be handed to a provider within a few seconds (an assumed goal, not a standard), a growing oldest-job age shows a delay before any user reports it. Provider errors by status code and dead-letter queue depth come next.
Who should be allowed to publish events to a notification service? +
Only authenticated internal callers. Use an OAuth 2.0 client credentials token or mutual TLS, then authorize by event type, so the order service can send order updates but not password resets. Give each caller a token-bucket quota and answer 429 past it.
Do opt-out and send-hour rules change the design? +
Yes. In the US, 47 CFR 64.1200(c)(1) limits telephone solicitations to 8 a.m. to 9 p.m. at the called party's location, and CAN-SPAM requires stopping commercial email within 10 business days of an opt-out. That forces a time zone per user, a suppression list and separate transactional and marketing types. This is design guidance, not legal advice.

References

  1. Amazon DynamoDB — Global tables, how they work (MREC, MRSC, last writer wins)
  2. Amazon DynamoDB — Time to Live (TTL)
  3. RFC 6749 — OAuth 2.0, client credentials grant
  4. RFC 8705 — OAuth 2.0 mutual-TLS client authentication and certificate-bound tokens
  5. US eCFR — 47 CFR 64.1200(c)(1), telephone solicitation hours (8 a.m. to 9 p.m.)
  6. 15 U.S.C. 7704 — CAN-SPAM, prohibition on sending after an opt-out (10 business days)
  7. GDPR Article 21 — Right to object (direct marketing)
  8. Google — Email sender guidelines (bulk senders, one-click unsubscribe, spam rate)
  9. Firebase — FCM throttling and quotas
  10. Apple — Sending notification requests to APNs (payload size, expiration, collapse id)
Written by
Sukhveer Kaur
Sukhveer KaurSoftware Developer & AI Engineer

Sukhveer is a software developer specialising in AI systems and backend engineering. She has hands-on experience designing agentic AI applications, working with large language model pipelines, autonomous agent frameworks, and cloud-native services in Java and Python. At InfoWok, she bridges the gap between cutting-edge AI research and practical implementation — helping developers understand and apply emerging technologies through clear, experience-backed writing.

New AI engineering guides, the day they ship

Real Python, production depth. No digest spam.

Comments