Razorpay's notification system treated every message the same. That sounds fair until you realize it meant a marketing blast could delay the one-time password someone needed to complete a payment.
At peak, MySQL became the bottleneck for guaranteed-delivery notifications, and — worse than slow — the system was egalitarian: a transactional OTP and a promotional SMS sat in the same line, so low-value bulk traffic could starve high-value transactional traffic exactly when it mattered most.
When one part of your system creates work for another (here: 'send this notification'), wiring them together directly means a slowdown in sending backs up into the thing that requested it. The fix is a queue in between: the producer drops a message and moves on; workers pull from the queue at their own pace. The two sides are now decoupled — a slow SMS provider no longer freezes your payment flow.
But a single queue has a flaw: it's first-come-first-served. Put a million marketing messages in front of one critical OTP, and the OTP waits behind the million. You need priority.
Razorpay moved to SQS-based decoupling: producers enqueue notifications; a pool of workers (an executor) consumes and dispatches to SMS/email/push providers. This removes MySQL as the throughput chokepoint and lets sending scale independently of the services requesting it.
On top, two essentials. Prioritization: critical transactional notifications (OTPs, payment confirmations) are routed and drained ahead of bulk marketing, so a promotional surge can't delay a payment. And a retry scheduler: because delivery must be guaranteed, failed sends are tracked and retried rather than silently dropped — turning 'best effort' into 'eventually delivered.'
Worth knowing
This is the same prioritisation instinct as Netflix's load shedding (#7), in a humbler setting: when resources are contended, the system must know which work is valuable. A queue alone gives you decoupling and durability; priority lanes give you the judgment to spend scarce delivery capacity on the OTP, not the coupon. Decoupling without prioritisation just moves the starvation into the queue.
The gap it reveals
'Add a queue' is a common reflex; the gap is knowing what a plain queue does and doesn't buy you. It decouples and (with retries) guarantees delivery — but it's FIFO, so without priority lanes your most important message can wait behind your least important. Recognising that contended notification systems need explicit prioritisation, plus a retry mechanism for guaranteed delivery, is the design-complete view.
In the interview room
"Design a notification service" rewards three moves: a queue to decouple producers from flaky providers, priority tiers so transactional beats promotional, and a retry/scheduler path for guaranteed delivery (with idempotency so retries don't double-send). Volunteering the priority and retry pieces — not just 'use a queue' — is what separates a complete design from a sketch.
The reframe
Decoupling with a queue is the easy, famous half of the answer. The half that separates a working system from a fair one is admitting your messages aren't equal and encoding that truth into the architecture. A system that treats a payment OTP and a coupon identically isn't simple — it's neglecting the most important decision it could make.
A queue buys you breathing room. Priority buys you the right outcome when the room runs out.
Primary source →
Razorpay Engineering — How Razorpay’s Notification Service Handles Increasing Load