Designing a Real-Time Messaging Platform
A WhatsApp-style messaging platform is not just WebSockets plus a database. It is a distributed protocol for moving one logical message across unreliable networks while preserving identity, durability, ordering, multi-device convergence, and understandable delivery state. All exact scale figures in this page are illustrative design assumptions unless explicitly sourced.
Problem Definition
We are designing a WhatsApp-style messaging platform for 1:1 chat, groups, realtime delivery, offline sync, receipts, multiple devices, media references, and presence. Optional extensions include end-to-end encryption, edits/deletes, reactions, and broadcast-style channels.
The running example used throughout the module is: Alice sends message M to Bob while Bob's laptop is online, Bob's phone is offline, and Alice loses the first ACK after the server already durably accepted the message.
Out of scope for this foundations page: voice and video calling, payments, a full Signal Protocol tutorial, moderation or ML platforms, full notification-provider internals, contact-discovery cryptography, and general-purpose object-storage design.
Visual Design Journey
Watch a two-user chat app grow into a globally distributed messaging platform — persistent connections, durable delivery, ordering, group fan-out, and multi-device sync, each earned by a real bottleneck along the way.
Functional Requirements
The core loop is still simple to state: connect, send, persist, route, deliver, acknowledge, and recover when one side is offline.
In scope: one-to-one chat, group messaging, online realtime delivery, offline delivery and sync, delivery/read receipts, multi-device support, media references, and presence or typing at architecture level. Broadcast or channel semantics can be discussed as a bounded extension rather than silently merged with ordinary group chat.
Product semantics should be stated explicitly. In this design, sender-visible delivered means at least one active recipient device acknowledged receipt; read is a later event and may be aggregated with conversation-level read high-watermarks.
Non-Functional Requirements
The ordering of priorities matters as much as the list itself.
Durability first
Once the server returns a positive sender ACK, the message has satisfied the durable write contract for this design. That does not mean every device has seen it yet; it means the system can recover and continue delivery after ordinary single-component failures.
Conversation ordering
Users need consistent order inside a conversation, not global order across the whole platform. The design therefore needs a conversation ordering authority, discussed further in Message Ordering & Sequencing.
Ephemeral tolerance
Presence and typing may be stale, coalesced, or dropped. Accepted user messages may not.
Scale Estimation
Illustrative assumptions help shape the design, but they are not claims about undocumented WhatsApp internals.
Example assumptions for this module: 500M DAU, 200M concurrent connections, 100B messages/day, about 1.15M messages/sec average, 1 KB/message effective stored size, and 30-day server retention. These numbers are here to reason about order-of-magnitude constraints, not to assert current production facts.
Arithmetic from those assumptions (illustrative): 100B messages/day × 1 KB ≈ 100 TB/day of stored message data, and 30-day retention gives about 3 PB before replication and indexes. If the sizing thought experiment of 50K connections per gateway held, 200M concurrent connections would need about 4,000 gateway instances before any headroom.
Several dimensions matter independently: concurrent sockets, message writes, fan-out amplification, reconnect bursts, presence churn, message-history reads, and media bytes. Connection count is distinctive, but it is not universally the first bottleneck under every incident or product shape.
Core Entities
The data model separates durable message history, slower-moving metadata, and ephemeral routing state.
Message
Canonical server message with message_id, conversation_id, sender_id, sender_device_id, payload or ciphertext, accepted timestamp, and conversation order metadata.
Device session
A device or session has its own device_id, auth context, routing state, and sync cursor. Multi-device is therefore a first-class concept, not an afterthought.
Conversation and membership
Conversation records and membership metadata decide who is authorized to send and which membership snapshot applies to each accepted message.
The registry is not a durable entity. It is ephemeral routing state described in Connection Registry & Multi-Device Routing.
API Design
Realtime messaging primarily uses WebSocket frames, with sync or fallback APIs for catch-up and history reads.
{
"type": "message.send",
"conversation_id": "conv-1",
"client_message_id": "cmid-123",
"payload": "ciphertext-or-text",
"content_type": "text"
}The send contract is explicit: authenticate sender, authorize conversation membership, validate payload, deduplicate or reconcile client_message_id, assign server identity and ordering metadata, durably persist, then ACK. That ACK means durable acceptance only—it does not mean Bob read the message, every Bob device received it, or push succeeded.
Reconnect and catch-up should use a cursor-based sync API rather than brute-force polling of every conversation. See Offline Message Sync.
Simple Architecture & Its Limits
A single server plus one database is still the right first sketch because it makes bottlenecks visible.
The simple version works while one server can hold the active sockets and one database can absorb the write rate. It breaks when many servers now hold different subsets of client connections and a sender can no longer directly know where the recipient is connected.
That is what earns the later connection gateway tier, routing registry, scalable message storage, and async fan-out—not a desire to copy a trendy architecture.
Persistent Connections & Connection Gateway
Persistent connections exist because polling is too wasteful and too latent for chat.
A dedicated gateway tier becomes useful when connection lifecycle work—authentication, heartbeats, reconnects, socket buffers, draining—needs to scale differently from message validation and persistence.
Every connect creates a device or session-specific route rather than a single per-user route. Multiple devices can be live at once, and reconnects use jittered backoff to avoid storms.
Read deeper: WebSocket Gateway Internals.
Connection Routing & Registry
The registry answers: which active device sessions for Bob currently have a usable route?
Model it conceptually as user_id -> {device/session -> gateway, connection_id, epoch}. That fixes the common oversimplification of user_id -> one gateway and makes multi-device routing explicit from the start.
Registry state is ephemeral and can be stale. If lookup or delivery fails, the message still remains correct because durable storage and later sync are the authoritative safety nets.
Read deeper: Connection Registry & Multi-Device Routing.
Message Durability & Offline Delivery
Durable acceptance comes before reliable delivery. Realtime routing improves latency; it does not replace durable message state.
A positive sender ACK means the server has durably accepted the logical message according to the write contract. It does not mean Bob was online, Bob read the message, or the push provider succeeded.
Offline recovery should use a durable user- or device-scoped sync cursor. Querying every conversation for sequence > last_seen is useful for specific history reads, but not as the primary reconnect protocol for users with many threads.
Read deeper: Offline Message Sync.
Message Ordering & Idempotency
Ordering and idempotency are the two mechanisms that turn retries and concurrent sends into one coherent conversation.
The server should not rely on a racy read-before-write dedup check. Instead it needs atomic idempotency handling so Alice's retry after a lost ACK resolves to the same accepted message.
Likewise, consistent hashing alone is not an ordering guarantee. The design needs a conversation ordering authority with ownership or fencing semantics so failover and rebalance do not create split ownership.
Read deeper: Message Idempotency and Message Ordering & Sequencing.
Delivery States & Acknowledgements
Sent, delivered, and read are separate distributed events.
In this design, delivered means at least one active recipient device acknowledged receipt. Internally, the system may still track richer per-device state so Bob's offline phone can converge later and late duplicate receipts can be ignored.
Receipt events may be delayed, duplicated, reordered, or batched. Read receipts especially benefit from high-watermark semantics like “read up to sequence 418” instead of one event per message.
Read deeper: Message Delivery Receipts and Message Read Receipts.
Presence Service
Presence and typing should look cheap because they are treated as lossy, scoped, ephemeral signals.
Use leases or TTLs, aggregate over a user's active sessions, and fan updates only to interested viewers or open conversations. Heartbeats are absorbed locally and do not each become a globally distributed presence update.
A presence outage should degrade online indicators and last-seen freshness, not core message delivery.
Read deeper: Presence Architecture and Typing Indicators.
Group Messaging & Fan-Out
Groups multiply the delivery problem, but not all large audiences are the same shape.
Avoid turning 256 or 1024 into universal laws. Thresholds are illustrative and should depend on activity, online fraction, receipt semantics, and infrastructure limits.
Interactive groups need membership semantics, ordering, and sometimes receipts. Broadcast or channel-style products behave more like store-once feed distribution. Treating both as the same thing hides an important architectural distinction.
Read deeper: Group Membership Consistency, Group Message Fan-Out, and Broadcast / Channel Messaging.
Multi-Device Synchronization
Multi-device is not just “deliver to more sockets.” It changes delivery semantics, read aggregation, and sync responsibilities.
A user may have several active devices plus one old or offline device. Incoming messages may fan out to multiple active devices, and the sender's own other devices may also need the sent message so the conversation list stays converged everywhere.
Receipts may be tracked per device internally and then aggregated to the user-visible state. New devices and reconnected devices recover through their own durable sync cursor, not by assuming another device will replay history for them.
Read deeper: Connection Registry & Multi-Device Routing and Offline Message Sync.
Media Architecture
Media shares conversation semantics but takes a separate byte path.
Authorize upload, upload directly or resumably to object storage, send a lightweight message containing the media reference and metadata, then let recipients fetch through an authorized object or CDN path. Multi-megabyte media bytes should not run through the text-message gateway path.
For upload, download, authorization, and E2EE media implications, see Media Message Architecture.
Storage & Partitioning
The durable message store, metadata store, and ephemeral caches solve different workload shapes.
conversation_id alone should not always be one physical partition forever. Long-lived or hot conversations often need bucketed partitioning such as (conversation_id, bucket) to bound physical size and hotspot behavior.
At small or moderate scale, relational storage can be entirely appropriate. At very high write rates, wide-column or distributed KV designs often fit better. The choice is workload-driven, not brand-driven.
Read deeper: Message Storage & Partitioning and Conversation Log vs Inbox.
Final Architecture
Put the full story together: authenticate the device, accept one logical send idempotently, assign conversation order, durably store, route to online devices, trigger offline wake-ups, and let reconnect sync repair what realtime routing could not finish.
In the running example, Alice sends M with a stable client_message_id. The server authorizes the send, deduplicates atomically, assigns message_id and sequence, writes durably, then loses the first ACK back to Alice. Alice retries with the same client ID and receives the original acceptance result. Bob's laptop gets M immediately because it has a live route. Bob's phone gets it later via sync from its device cursor. Alice sees delivery only after one Bob device acknowledges it.
That end-to-end path is why the architecture has several distinct planes: connection, message write, delivery, sync, conversation or group management, ephemeral presence or typing, and optionally encryption-aware device state. If you can narrate that flow end to end without hand-waving, the design holds together.
- Idempotent send comes first: a stable client_message_id lets Alice retry safely after a lost ACK and receive the original acceptance result.
- Persist before you acknowledge: assign the conversation sequence and write durably, so realtime routing is an optimisation rather than the source of truth.
- Realtime delivery is best-effort; offline devices and missed routes are repaired by reconnect sync, so the gateway never has to guarantee delivery on its own.
- Several independent planes (connection, write, delivery, sync, group management, ephemeral presence/typing) fail and scale separately, so size each one on its own dimension.
- Connection count is distinctive but not universally the first bottleneck; fan-out amplification, reconnect bursts and media bytes can dominate depending on the incident.