Your notification system works great at 100 messages a minute. Then your app goes viral, a flash sale hits, and suddenly your OTPs are stuck behind 2 million promotional pushes.
Sooner or later, every notification system needs a message queue. The hard part is picking one. Kafka, RabbitMQ, SQS, Pub/Sub and Kinesis all "move messages," but they behave very differently when you need ordering, retries, replays or priorities.
This guide walks through the factors that matter for notifications, compares the five most common options, and ends with a checklist you can use to decide.
First, what do notifications need from a queue?
Forget feature lists for a second. Here's what actually matters, with a real scenario for each.
Scalability
Your sports app sends a push whenever a team scores. Traffic is quiet until a derby match, then every user gets a notification within seconds. You need a queue that scales out without redesign.
Message retention
You send transaction alerts in real time. One night your consumer crashes for 30 minutes. If the queue discards messages that aren't consumed, those users never hear about their money moving.
With retention, messages wait in the queue (for an hour, a day, longer) and are processed when the consumer comes back.
Low latency
A fraud team needs to be alerted the moment a suspicious transaction appears. Delay matters here.
A caveat: Redis Pub/Sub is often cited for this, but it's fire-and-forget. If no subscriber is listening, the message is gone. For anything you can't afford to lose, use Redis Streams or a durable broker.
High throughput
A food delivery app sends status updates ("being prepared", "on its way") for every order. Volume is high, and many consumers need to process it in parallel. This is where Kafka and Kinesis shine.
Message ordering
A trading platform sends market alerts. If "price dropped" arrives after "price recovered," users act on stale information. You need ordering guarantees, at least per user or per entity.
Prioritization
A hospital monitoring system alerts nurses when a patient's heart rate crosses a threshold. That alert can't wait behind a batch of routine reports.
One thing to know: most queues don't have native priority. The usual pattern is separate queues or topics per priority:
TOPICS = {
"critical": "notif.critical", # OTP, security, health alerts
"high": "notif.high", # transactional
"low": "notif.low", # promos, digests
}
producer.send(
TOPICS[msg.priority],
key=msg.user_id.encode(), # same user -> same partition -> ordered
value=payload,
)
Each topic gets its own consumers and throughput limits, so a promo blast can't starve your OTPs. (RabbitMQ is the exception, with native priority queues.)
Dead letter queues (DLQs)
An e-commerce site sends order confirmations. Some email addresses are invalid and fail every time. Without a DLQ, those messages are retried forever or block the line. With a DLQ, failures move aside where you can inspect them and fix the cause.
{
"RedrivePolicy": {
"deadLetterTargetArn": "arn:aws:sqs:us-east-1:123456789012:notif-dlq",
"maxReceiveCount": 5
}
}
That's an SQS redrive policy: after 5 failed receives, the message goes to the DLQ.
The five contenders, side by side
| RabbitMQ | Kafka | AWS SQS | Google Pub/Sub | AWS Kinesis | |
|---|---|---|---|---|---|
| Model | Broker with exchanges and queues (AMQP) | Distributed log, consumer groups | Queue (pull) | Topics and subscriptions (push or pull) | Streams split into shards |
| Ordering | FIFO per queue | Per partition | FIFO queues, per message group | Per ordering key (opt-in) | Per shard / partition key |
| Latency | Very low | Low | Low | Low | Low |
| Throughput | Moderate to high | Very high | Very high | High | High (shard-bound) |
| Retention / replay | Until acked (Streams plugin adds replay) | Configurable, replayable | Up to 14 days, no replay | Configurable, can seek back | 24h up to 365 days, replayable |
| Native priority | Yes | No (use separate topics) | No (use separate queues) | No (use separate topics) | No (use separate streams) |
| Dead letter queue | Dead-letter exchanges | DIY (a DLQ topic) | Native | Native | No native DLQ (handle in the consumer) |
| Ops burden | Moderate (self-host or managed) | Heavy if self-hosted, managed options exist | Fully managed | Fully managed | Fully managed |
| Pricing | Infra cost if self-hosted | Infra cost if self-hosted | Pay per request | Pay per volume | Pay per shard / volume |
| Access control | vhosts and permissions | ACLs | IAM | IAM | IAM |
Limits like max message size change over time, so check each provider's docs before you design around them.
Which one should you pick?
Here are three common situations.
🚀 Startup with low-volume transactional notifications → RabbitMQ (or SQS)
Easy to set up, simple to reason about, and flexible routing. It has native priority queues and dead-letter exchanges, so you get a lot of notification-friendly features without much ceremony. If you're all-in on AWS and want zero ops, SQS is a strong alternative.
📈 Mid-sized company, high volume, low latency → Kafka
Distributed, high throughput, and great for fan-out: one event can feed email, push and analytics consumers at once. Replication and replay are strong, and you can reprocess history when something goes wrong. The trade-off is operational complexity, so consider a managed offering.
🏢 Enterprise on AWS with many microservices → Kinesis (plus SNS/SQS where it fits)
Fully managed, integrates natively with Lambda and the rest of AWS, and handles large volumes. Kinesis is a streaming service rather than a classic queue, so many teams pair it with SQS or SNS for simple fan-out and retry patterns.
At-least-once delivery means you need idempotency
Almost every queue above delivers messages at least once. Retries and rebalances can deliver the same message twice, and a user getting the same "payment received" push twice is a bug.
def handle(msg):
if already_processed(msg.id): # e.g. Redis SETNX or DB unique key
return
send_notification(msg)
mark_processed(msg.id)
Whichever queue you pick, build this in from day one.
Latency vs throughput: you can't max both
Optimizing for one tends to cost you the other.
- Low latency needs fast ingest and delivery, which uses more CPU, memory and network per message.
- High throughput relies on batching and parallelism, which can add a little delay per message.
RabbitMQ leans toward low-latency, real-time delivery. Kafka leans toward high-throughput streaming, but it's tunable (settings like batch size and linger time let you trade one for the other). Even then, hardware and architecture set the limits.
A practical rule: use low-latency settings for your critical queue (OTPs, alerts), and throughput-optimized settings for the low-priority bulk queue.
A checklist for deciding
Run through these before you commit.
- Define your numbers. Message volume, latency target, reliability needs.
- Pick your trade-off. Latency or throughput first?
- Plan for growth. Can it scale horizontally as your user base grows?
- Check ecosystem fit. Your languages, frameworks and existing cloud provider.
- Count the operational cost. Setup complexity, monitoring, and what your team already knows.
- Check security. Durability, access control and encryption in transit.
- Budget it. Infrastructure, managed service fees and maintenance time.
- Prototype and load-test. Don't trust benchmarks, including the ones in this post. Test with your own traffic.
Running multi-cloud or hybrid?
A few things to watch for:
- Multiple queue technologies. Different components may use different brokers. Make sure your team can operate all of them.
- Standard protocols. Prefer widely supported protocols like AMQP or MQTT where you can, and keep message formats consistent to avoid fragmentation.
- Complexity vs flexibility. Cross-environment connectivity adds moving parts. Make sure the flexibility is worth it.
Wrapping up
There's no single best message queue, only the best fit for your volume, latency needs, team and cloud. Balance performance, reliability, ease of use and ecosystem, and revisit the decision as your notification system grows.
And if you'd rather not build and operate this whole layer yourself (queues, priorities, retries, fallbacks), that's what we built SuprSend for. Full disclosure: we work on it, so take that into account.
Your turn: which queue are you running notifications on today, and would you pick it again? Tell us in the comments. Horror stories welcome.
Top comments (0)