Loading post
Jul 18, 2026

A chat bubble usually does not travel directly to someone else's screen. The sender's device, the service's servers, push-notification infrastructure, and the recipient's devices cooperate. Even with end-to-end encryption, delivery still needs metadata such as destinations, devices, and timing. Japanese version: blog-how-messaging-works.md.

When you press Send, the app assigns a local ID and draws a pending bubble immediately. In the background, it submits the message to a server. The server authenticates the request, stores the message, and fans it out. If the recipient is connected, it uses a live channel. If the app is suspended, the service asks APNs or FCM to announce that data is waiting. The recipient then fetches, decrypts, stores, and acknowledges the message; delivery and read status can travel back.
“Created locally,” “accepted by the service,” “delivered to a device,” and “opened by the recipient” are different facts.
Waiting for the network before drawing the message would make the app feel slow on a train or congested connection. Most messaging interfaces therefore use an optimistic UI:
client_message_id.The interface feels instant because it predicts success, not because the entire delivery completed instantly.
While the app is open, it can maintain a WebSocket, long-lived HTTP request, or proprietary connection. The server can deliver events without waiting for the app to poll.
Mobile operating systems suspend background apps to preserve battery and memory. An app cannot assume that its private connection will live forever. It therefore uses an OS-level push service.
Push is best understood as a doorbell, not the authoritative parcel. A notification may include content, but payload size, privacy, expiration, coalescing, and delivery constraints apply. Apple explicitly describes remote-notification delivery as not guaranteed. Durable state and resynchronization must recover the truth.
Normal mobile failures include:
Without retries, messages disappear. With blind retries, they duplicate. A common solution uses client_message_id as an idempotency key.
Unique constraint: (sender_id, client_message_id)
First attempt:
absent → persist → return server_message_id
Retry:
already present → do not persist again; return the same result
An experience that looks “exactly once” is often built from at-least-once retries plus receiver-side deduplication, not from a network that transmits exactly once.
Two people can send at almost the same time. Their clocks may disagree and their network paths differ. Sorting exclusively by client timestamps can reorder the conversation.
The server can assign a monotonically increasing sequence per conversation or define a server-observed order.
| Value | Useful for |
|---|---|
client_created_at | Approximate user-facing creation time |
server_received_at | Server-side audit time |
conversation_seq | Stable ordering and gap detection |
server_message_id | Durable identity |
In a large, multi-region group, a strict total order can conflict with latency and availability. Whether order is guaranteed per conversation, per sender, or only on a best-effort basis is a product decision as much as an implementation detail.
With multiple devices, the product must define whether delivery to any device is enough and how a read on one device propagates to all others.
With E2EE, only participant devices normally hold keys capable of reading message content. The server routes ciphertext.
E2EE does not necessarily make the service oblivious. Delivery may expose metadata such as:
Key lifecycle is the difficult part: adding a new device, revoking a lost one, preventing a removed member from reading future traffic, protecting old traffic after a later compromise, and recovering or backing up keys across devices.
They complement each other. E2EE ciphertext is still carried through TLS so intermediaries cannot observe or alter the surrounding connection as easily.
Expanding a group post into every recipient's inbox at write time makes reads fast but large-group writes expensive. Storing one post and composing per-user state on read makes writes lighter but complicates unread counts, permission changes, and retrieval. Real systems often combine both strategies.
A device can send last_seen_seq; the server returns later messages plus edits, deletions, reactions, and receipts. A sequence gap triggers repair. A device offline for months may need a snapshot followed by a delta stream.
A famous live stream or enormous group can overload the node that assigns conversation order. User IDs are easy to shard, while conversation IDs are where ordering is desired. Choosing the partition boundary is a core scaling trade-off.
Averages hide tail failures. Separate at least:
With E2EE, message bodies should not be diagnostic logs. Message IDs, privacy-preserving route labels, phase timestamps, and error classes have to provide enough evidence.
| Misconception | Reality |
|---|---|
| A check mark proves the person read it | It may represent acceptance, delivery, or read status; the product defines it |
| If push arrives, the message is safely delivered | Push can be delayed or omitted; app sync is authoritative |
| WebSocket prevents message loss | It is a connection mechanism; persistence, acknowledgements, and retries are application concerns |
| E2EE means the service knows nothing | Content may be hidden while routing metadata remains |
| An exactly-once API eliminates duplicates | Strict exactly-once across boundaries is difficult; idempotent IDs do most of the work |
A messaging app does not feel reliable because of one fast protocol. Optimistic UI, retryable sends, idempotent storage, sequence numbers, online delivery, push as a doorbell, and delta synchronization combine to create the single experience of “I sent it, and it arrived.”