See It Flow
System Design · 18 chapters · complete end-to-end walkthrough

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.

Mangesh MandlikLast updated:
Chapter 01

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.

Interactive

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.

Chapter 02

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.

Chapter 03

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.

Chapter 04

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.

Chapter 05

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.

Chapter 06

API Design

Realtime messaging primarily uses WebSocket frames, with sync or fallback APIs for catch-up and history reads.

jsonThe client-generated idempotency identity exists before the first attempt.
{
  "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.

Chapter 07

Simple Architecture & Its Limits

A single server plus one database is still the right first sketch because it makes bottlenecks visible.

Initial ArchitectureClient, app server, database — the simplest possible start.Chat Appmobile clientApp ServermonolithPostgreSQLmessages · usersRESTSQL
Start simple, then split connection management, durable storage, and fan-out only when the numbers force it.

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.

Chapter 08

Persistent Connections & Connection Gateway

Persistent connections exist because polling is too wasteful and too latent for chat.

Real-Time Delivery · WebSocket vs. PollingClientGatewayWebSocketMsg ServicepersistentpushWEBSOCKETClientServerany new messages?repeat every 3s...POLLING
Persistent bidirectional sockets cut empty-request overhead and let the server push immediately when Bob is online.

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.

Chapter 09

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.

Chapter 10

Message Durability & Offline Delivery

Durable acceptance comes before reliable delivery. Realtime routing improves latency; it does not replace durable message state.

Offline Delivery · store and sync1. STORE WHILE OFFLINESenderMsg ServiceMsg StorepersistedRecipientofflineX2. SYNC ON RECONNECTRecipientonlineMsg ServiceMsg Storeread batchreconnectfetch
Persist first, route second, then recover later through sync if the recipient was offline or the route failed.

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.

Chapter 11

Message Ordering & Idempotency

Ordering and idempotency are the two mechanisms that turn retries and concurrent sends into one coherent conversation.

Message Ordering · per-conversation sequencesUser Aseq: 1 "Hello!"seq: 2 "How are you?"User Bseq: 3 "I'm good!"conversation_id = "conv_abc123"partition key → monotonic sequenceconversation-level ordering guaranteed
One ordering authority per conversation is what turns concurrent sends into a shared sequence.

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.

Chapter 12

Delivery States & Acknowledgements

Sent, delivered, and read are separate distributed events.

Delivery States · Sent → Delivered → ReadSENTSenderServer✓DELIVEREDServerRecipient✓✓READRecipientSenderread ack✓✓each state triggers an ack back to the senderenum Status { SENT, DELIVERED, READ }msg.status = DELIVERED // server-side ACK// read receipt sent by recipient device
A checkmark change in the UI corresponds to a distinct event somewhere in the backend protocol.

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.

Chapter 13

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.

Chapter 14

Group Messaging & Fan-Out

Groups multiply the delivery problem, but not all large audiences are the same shape.

Group Messaging · Write Fan-Out vs. Read Fan-OutWRITE FAN-OUTMessageInbox 1Inbox 2Inbox 3Inbox 4100 members = 100 writesREAD FAN-OUTMessageGroupM1M2M3M41 write, reads from groupsmall groups → write fan-out | large groups → read fan-out
Small interactive groups and huge audiences may reuse some components while still needing different fan-out mechanics.

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.

Chapter 15

Multi-Device Synchronization

Multi-device is not just “deliver to more sockets.” It changes delivery semantics, read aggregation, and sync responsibilities.

Multi-Device Sync · per-device cursorsSync Enginecursor managerUserPhonecursor: 847Laptopcursor: 845Tabletcursor: 843syncsyncsynceach device tracks its own read cursor · independent sync
Bob's laptop can get M now while Bob's phone later catches up from its own cursor.

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.

Chapter 16

Media Architecture

Media shares conversation semantics but takes a separate byte path.

Media Message Pipeline · upload / downloadUPLOAD PATHSenderuploadMedia SvcObject StoreS3 / blobMsg Servicemetadata refmedia_urlDOWNLOAD PATHRecipientdownloadCDNedge cacheObject StoreGETmissmedia separated from messaging · CDN for low-latency delivery
The message carries a reference; upload/download of the actual bytes uses dedicated storage and CDN infrastructure.

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.

Chapter 17

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.

Chapter 18

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.

Complete ArchitectureDNSLBGW 1GW 2GW 3RegistryMsg SvcCassandramsg storeKafkaevent busFan-OutworkersPresenceonline statusMedia SvcObject StoreS3CDNedgepersistent connections · async fan-out · independent scaling
The full architecture combines connection, write, delivery, sync, group, presence, push, and media concerns without confusing which state is durable and which is ephemeral.

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.

Cascading Failure · latency propagationLBGatewaythreads fullMsg Service2200msMsg Store2000msSLOWCircuit Breakertimeout: 200msfail fast · return cached · protect upstreamthreads exhausted2200ms2000ms
Failure isolation matters because a slow storage or fan-out path can still push pressure backward through the rest of the system.
Key takeaways
  • 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.