Designing a Message Forward / Fanout System

Designing a Message Forward Fanout System

Designing a Message Forward / Fanout System

A complete, ground-up walkthrough of how WhatsApp, Slack, Discord, Twitter/X and every large chat platform take a single message from a single sender and deliver it — reliably, in order and at low latency — to millions of recipients across devices, networks and continents.

01

The Big Idea, in One Breath

A message forward / fanout system is the machinery that takes one incoming write — a chat message, a post, an event, a notification — and duplicates it into every place where it needs to appear: N recipients’ inboxes, M devices per user, a search index, a moderation queue, a push-notification service, and often an audit log. It must do that quickly, in order per conversation, without losing or duplicating a single copy, even when networks are slow and downstreams occasionally fail.

The tricky part is not the copying — it is the economics of it. Fanning out one message to ten friends is trivial. Fanning out one message from a celebrity to fifty million followers, while the same platform is doing that for a hundred other celebrities in the same second, is one of the classic hard problems of internet-scale software.

Analogy

Picture a newsroom the moment a big story breaks. One reporter files a single story. Within minutes, that same story must reach the front page, the mobile app, the newsletter, the wire services, the search index, the archive and every subscriber’s home. Each channel has different urgency, format and reliability needs, but every copy must be the same story, in the right order, without the newsroom itself grinding to a halt. A fanout system is that newsroom — but running billions of times a day, silently, in software.

1 → 10ⁿ
Write amplification
per message
< 500 ms
Median delivery
to the last recipient
100%
Exactly-once
per subscriber
02

What Fanout Really Is

Before we design one, we need to pin down exactly what the system does — and what it does not. Fanout is not caching, not queuing, not pub/sub on its own, and not push notifications. It is the layer that owns the question “who should see this, and how do we get it to them?”

2.1 A Working Definition

A message forward / fanout system is a distributed component that, given a single accepted write (message, post, event) and a set of subscribers (recipients, followers, channel members, downstream services), guarantees:

  • each subscriber receives a copy in a bounded time window,
  • each subscriber receives it at-least-once and can deduplicate to exactly-once,
  • ordering is preserved per conversation / topic / partition key (not necessarily globally),
  • the system is able to keep up during viral traffic without collapsing back onto the origin write path, and
  • every delivery is observable, replayable and auditable.

2.2 Where You Encounter It

Chat

1:1 & Group Messaging

WhatsApp, iMessage, Signal, Telegram — every message is fanned out to the other participants’ devices and their offline mailboxes.

Feeds

Social Feeds & Timelines

Twitter/X, Instagram, TikTok, LinkedIn — a post is fanned out into millions of follower home timelines (fanout-on-write) or served from the author’s outbox (fanout-on-read).

Collab

Team Collaboration

Slack, Teams, Discord — a channel message is fanned out to every online member and buffered for those who are offline.

Events

Event Bus & Webhooks

An order.placed event is forwarded to inventory, billing, fraud, analytics, search and 20 other downstreams — each with its own SLA and failure model.

2.3 What It Is Not

Fanout is not the same as a message broker, a push-notification service or an inbox database — even though it uses all three. It is the coordination layer that decides who, when, how and with what guarantees, and delegates the actual byte-moving to those specialised services.

💡
Mental Model

The broker is a pipe. The push service is a shout. The inbox is a mailbox. Fanout is the postmaster: it looks at the envelope, decides which pipes to use, which mailboxes to fill, which shouts to make, and keeps a record of every copy sent so nothing is lost and nothing is delivered twice.

03

Why It Matters So Much

Fanout is one of the most quietly load-bearing systems in modern software. Every social feed, every chat app, every event-driven platform depends on it. When it works, nobody notices. When it breaks, users see missing messages, out-of-order threads and stale timelines — and the platform’s brand takes the hit.

3.1 The Business & Human Problem

  • Trust in real-time. Users assume “send” means “delivered.” A single missing message erodes trust faster than any UI polish can rebuild it.
  • Cost of write amplification. One popular post can create tens of millions of writes. Naively fanning out on the write path can quickly bankrupt the storage tier.
  • Cost of read amplification. Naively fanning out on the read path can burn CPU on every timeline load and turn a hot celebrity into a hot key.
  • Regulatory audit. In finance, health and safety domains, every downstream copy must be traceable to the originating event, including retries and failures.
  • Operational sanity. Fanout is where blast radius lives. A single misconfigured downstream can turn a healthy system into a global incident within minutes if fanout is unbounded.

3.2 What Makes It Uniquely Hard

Harder than pub/sub

  • Subscriber sets are dynamic, per-message and enormous.
  • Per-recipient state (last-read cursor, mute, block, do-not-disturb) has to be respected.
  • Ordering per conversation must survive retries and failovers.

Harder than queueing

  • Fanout is a distribution problem, not a single-line problem.
  • Hot fanout keys (celebrities, popular channels) break naive sharding.
  • Delivery guarantees must survive downstream partial outages.
The Core Motivation

A fanout system exists to make one small truth — “this message was sent” — instantly, silently and reliably true in a million other places at once. Every design decision in this chapter serves that quiet, load-bearing promise.

04

The Building Blocks

A production fanout system is a small constellation of focused services. Each has one narrow job; the leverage is in how they compose.

4.1

Ingress / Write API

The single, authenticated entry point for new messages or events. Validates, assigns IDs, appends to the write-ahead log, and returns quickly.

4.2

Write-Ahead Log

Durable, ordered log (Kafka, Pulsar, Kinesis) that is the authoritative source of truth. Fanout workers consume from here, never from the user API.

4.3

Subscriber Resolver

Given a message, returns the set of recipients: chat participants, channel members, followers, downstream services. Cached, sharded, and versioned.

4.4

Fanout Planner

Decides how to fan out this specific message: fanout-on-write, fanout-on-read, hybrid, or async batch — based on subscriber-set size and hotness.

4.5

Delivery Workers

Stateless workers that read plans from the queue and push copies into the correct sinks (inbox DB, push service, websocket gateway, external webhook).

4.6

Per-User Inbox Store

Ordered, append-only per-recipient inbox for offline / historical access. Sharded by userId. Reads support pagination and sync.

4.7

Real-Time Push Gateway

WebSocket / MQTT / HTTP-push fleet that maintains persistent connections and delivers messages to online devices with sub-second latency.

4.8

Presence & Device Registry

Tracks which devices a user has, which are online, and which push tokens are valid. Consumed by the planner to skip dead endpoints.

4.9

Dedup / Idempotency Store

Records (messageId, subscriberId) pairs already delivered. Guarantees at-most-once effect even when at-least-once delivery is used.

4.10

Retry & Dead-Letter Queue

Handles downstream failures with exponential backoff, isolates poison messages to a DLQ, and provides an operator UI for replay.

4.11

Policy & Fairness Layer

Rate limits, per-tenant fairness, do-not-disturb rules, mute/block enforcement, geographic residency constraints.

4.12

Observability

Metrics (fanout-out ratio, delivery latency, retry rate, DLQ depth), traces per messageId, dashboards per tenant / channel / region.

05

Fanout Strategies: On-Write, On-Read, Hybrid

The single most consequential design decision is when the copy is made. There are three canonical strategies, and mature systems use all three — but for different traffic shapes.

5.1 Fanout-on-Write (Push Model)

At write time, the system materialises the message into every recipient’s inbox. Reads are then cheap and O(1). Great for chats, group messaging and follower counts in the thousands.

  • Pros: Fast reads, simple timeline logic, easy per-user personalisation.
  • Cons: Write amplification proportional to fanout size. A 50M-follower post produces 50M inbox writes.
  • Fits: 1:1 chat, small groups, low-fanout feeds.

5.2 Fanout-on-Read (Pull Model)

At write time, the message is stored only in the author’s outbox. At read time, the recipient’s timeline is composed by fetching from the outboxes of everyone they follow.

  • Pros: Constant, tiny write cost regardless of how many followers.
  • Cons: Reads become expensive; timeline composition on every scroll; hard to personalise cheaply.
  • Fits: Very-large-fanout accounts (celebrities), low-read-frequency users.

5.3 Hybrid Fanout

The system classifies producers and recipients by hotness and picks a strategy per pair:

Hybrid decision (simplified)
function decide_fanout(msg, author, subscribers):
    if subscribers.count <= SMALL_THRESHOLD:                # e.g. 10_000
        return "fanout_on_write"

    if author.is_hot_producer():                             # celebrity
        return "fanout_on_read"                              # merge at read time

    # medium producers: split
    hot_subs   = subscribers.filter(is_active_this_hour)
    cold_subs  = subscribers - hot_subs
    return {
        "hot" : { "strategy": "fanout_on_write", "targets": hot_subs  },
        "cold": { "strategy": "fanout_on_read",  "targets": cold_subs }
    }

5.4 Comparison at a Glance

StrategyWrite costRead costWhen it shines
On-write (push)O(N subscribers)O(1)Chats, small groups, low fanout
On-read (pull)O(1)O(K followees)Celebrities, low-read users
HybridBoundedBoundedReal social platforms at scale
Async batchDeferredDelayedNon-real-time notifications, digests
💡
Rule of Thumb

If your fanout size looks like a straight line, use on-write. If it looks like a power-law with a fat tail, use hybrid and put the tail on on-read. Never let a single celebrity decide the write budget for the whole platform.

06

Delivery Patterns: Order, Dedup and Guarantees

Deciding who is only half the story. The other half is in what order, with what guarantees, and how to survive downstream flakiness. Get these wrong and users see interleaved threads, duplicate notifications, and phantom messages.

6.1 Ordering: Global Is Fiction, Per-Key Is Truth

Global ordering across billions of messages is neither achievable nor useful. What users actually notice is ordering within a conversation, channel or topic. So the system:

  • partitions the write-ahead log by conversationId / channelId / topicKey,
  • ensures each partition is consumed by exactly one worker at a time,
  • and preserves that partition’s order through retries via idempotent, monotonically increasing sequence numbers.

6.2 Delivery Semantics

SemanticsWhat it meansCost
At-most-onceFire and forget; may lose messagesCheapest; almost never acceptable
At-least-onceGuaranteed delivery; possible duplicatesDefault choice; needs consumer dedup
Exactly-once (effective)At-least-once + idempotency storeHigher latency & storage; user-facing correctness

Real systems almost always ship at-least-once on the wire and rely on the dedup store to produce exactly-once effects. Insisting on true exactly-once end-to-end usually costs more than it delivers.

6.3 Idempotency Keys

Every delivery attempt carries a stable key: (messageId, subscriberId). The delivery worker checks the dedup store before applying the effect. Keys are TTL-bound to keep storage bounded; the TTL is chosen to exceed the worst-case retry window.

6.4 Backpressure & Retry

Backpressure

  • Delivery workers must slow ingestion when downstreams are struggling, not overrun them.
  • Queue depth, not CPU, is the right auto-scale signal.

Retry & DLQ

  • Exponential backoff with jitter for transient failures.
  • Poison messages go to a dead-letter queue with reason codes for manual replay.
i
Design Note

A common bug: retries succeed but the ack path fails, and the same message gets re-delivered forever. Solution: acknowledge before attempting the side-effect only when the effect is safe on replay, and always store the dedup key first when it is not.

07

Streaming vs Batch, Monolith vs Distributed

Two architectural axes must be picked early. Both decisions have long, expensive tails.

7.1 When Does Fanout Fire?

  • Streaming — every accepted write triggers immediate fanout. Required for chat and real-time feeds.
  • Micro-batching — group messages by conversation for a few tens of milliseconds and fan out in one pass. Great for high-fanout collaborative apps.
  • Batch / digest — deferred fanout used for email digests, weekly newsletters and low-priority notifications.

7.2 Deployment Shape

ShapeWhen it fitsTrade-offs
Monolith serviceSmall startup, single tenant, < 1k msg/sFast to build; caps at one machine’s throughput
Stream processor (Flink / Kafka Streams)> 10k msg/s, per-conversation state, multi-tenantGreat throughput; ops-heavier; exactly-once by construction
Actor / stateful service (Orleans, Akka)Long-lived conversations with rich state (typing, presence)Clean per-conversation model; needs sharding & failover
Serverless functionsVery bursty, low sustained traffic (webhooks)Cheap at rest; cold starts hurt sub-second SLAs

7.3 Sharding Strategy

The natural unit is the conversation / channel / topic. The write-ahead log is keyed by that identifier, and each partition is consumed by exactly one worker. Delivery workers are stateless and horizontal — they can be scaled independently of the log.

7.4 Hot Fanout Keys

A single celebrity account, a viral incident channel, or a “#general” in a 100k-employee Slack can saturate one partition. Techniques that help:

  • Sub-key splitting: split a hot key into N shards (channel:123#0..N) and reassemble in order downstream.
  • Fanout tiering: put hot producers on their own worker pool with priority quotas.
  • Sampling low-value events (typing indicators, cursor updates) during peaks.
Peak Warning

A viral event can 100× one partition’s traffic in seconds. Auto-scale on partition lag, not global CPU, and pre-warm delivery workers before predictable peaks (news events, launches, Super Bowl).

08

End-to-End Flow: One Message’s Life

Enough abstraction. Let us follow one message — a group chat post to 800 people — from “Send” on the sender’s phone to the last recipient’s notification.

1

Ingress accepts the write

Client posts to /v1/messages with an idempotency key. Ingress validates auth, assigns messageId=m_9f2a, appends to the write-ahead log partition for conversationId=c_44e1, and returns 202 Accepted.

2

Fanout planner picks a strategy

A worker consuming partition c_44e1 pulls the message. Subscriber resolver returns 800 participants. Planner picks fanout-on-write (bounded size, low producer hotness).

3

Plan is split into delivery tasks

800 delivery tasks are enqueued, keyed by (messageId, userId). Each carries the payload, dedup key, priority and target sinks (inbox + push + search).

4

Delivery workers run in parallel

Stateless workers pull tasks. For each user they: (a) idempotency check, (b) append to per-user inbox with the conversation’s sequence number, (c) look up presence.

5

Online devices receive over WebSocket

For each user marked online, the push gateway delivers the payload over their persistent socket. Median latency to online users: ~200 ms end-to-end.

6

Offline devices get push notifications

For offline users with valid push tokens, the worker calls APNs / FCM. If tokens are dead, presence & device registry is updated and the message stays in the inbox for next login.

7

Downstream sinks receive their copies

Search index consumes the same log for full-text search. Analytics gets an event. Moderation queue gets a copy if content flags trigger.

8

Retries, DLQ & observability

A handful of tasks fail on flaky mobile push. Exponential backoff kicks in; three retries land within the SLA. A single poison payload lands in DLQ with a reason code for the on-call to inspect.

09

Quality Attributes: The “-ilities”

The non-functional targets for a fanout system are unusual: it must be simultaneously fast, correct, elastic and cheap — and be all four during the worst hour of the year.

Perf

Delivery Latency

Median < 500 ms to the last online recipient; P95 < 2 s. Offline delivery through push completes within seconds after presence returns.

Perf

Throughput

Sized for peak: millions of ingested writes/second globally, tens of millions of delivered copies/second at peak fanout.

Corr

Ordering

Per-conversation ordering is inviolable. Global ordering is not attempted; the API contract makes that explicit.

Corr

Delivery Semantics

At-least-once on the wire, exactly-once effect via idempotency store. Zero silent drops.

Rely

Reliability

Downstream outages are absorbed by retry + DLQ. A dead push provider does not break the inbox path.

Scal

Scalability

Sharded by conversation. Delivery workers scale on partition lag; hot keys are sub-sharded automatically.

Avail

Availability

Graceful degradation: if the push gateway is down, inbox is still updated; when it comes back, catch-up delivers the tail.

Observ

Observability

Every messageId is traceable through ingress, log, planner, worker, sink. Fanout-out ratio and DLQ depth are top-line SLOs.

9.1 The Latency Budget

HopTargetHow
Ingress accept< 30 msWarm workers, in-VPC log append with quorum ack
Log → worker< 50 msKafka in-region, tight commit intervals
Resolver + planner< 30 msCached subscriber sets, in-memory decision table
Delivery worker per recipient< 40 msBatched idempotency check + inbox append
Push gateway to online device< 100 msPersistent socket, pre-serialised payload
Total to last online recipient~400–500 ms P95Within perception of “instant” for chat/feed
“Fanout is the load-bearing wall of every real-time platform. Silent, invisible — and the first thing that cracks under weight.”
10

Common Pitfalls & Trade-offs

Every fanout system, in production, gets bitten by the same handful of subtle bugs. Knowing them turns quarters of firefighting into a paragraph in a design review.

10.1 Ten Traps We’ve All Fallen Into

1

Hot celebrity kills the write path

One 50M-follower post takes down inbox writes for everyone else. Split producers by hotness; put celebrities on the pull path.

2

No idempotency key

Client retries create duplicate messages. Every write carries a client-supplied idempotency key; the API dedups server-side.

3

Ack before durable

Ingress acknowledges before the log append is quorum-safe. A single broker failure loses messages. Always ack after log durability.

4

Global ordering promised

API doc says “messages arrive in order.” They do — per conversation. Users then discover cross-conversation interleaving is not ordered. Document the guarantee precisely.

5

Push token graveyard

Dead APNs / FCM tokens are retried forever. Presence & device registry must age out tokens on hard failures.

6

Retry storm on downstream outage

Single sink flakes; retries pile up and take out neighbours. Circuit-breaker per sink; shed load or park the queue rather than hammer.

7

DLQ nobody reads

Messages quietly land in DLQ for months. Add SLOs on DLQ depth and an on-call rotation for replay/discard decisions.

8

Multi-tenant unfairness

One noisy tenant hogs all delivery workers. Fair-share scheduling with per-tenant quotas.

9

Subscriber-set staleness

Someone gets kicked from a group but keeps receiving messages for an hour. Version subscriber sets and honour the version in the delivery task.

10

No shadow mode for new sinks

Ship a new downstream, it collapses under real fanout traffic. Every new sink runs in shadow with mirrored traffic first.

10.2 The Trade-offs You Cannot Avoid

Push vs Pull

  • Push (on-write) is fast to read, expensive to write for high fanout.
  • Pull (on-read) is cheap to write, expensive to read on scroll.
  • Hybrid, with a per-user or per-pair decision, is the only strategy that scales beyond a few million users.

Consistency vs Latency

  • Strict per-recipient consistency across devices is expensive.
  • Best-effort per-device with a strongly consistent inbox is the pragmatic compromise.
ADR-01Accepted
Context

We must decide the delivery semantics guaranteed to clients and downstream systems.

Decision

Adopt at-least-once wire delivery with server-side idempotency keyed on (messageId, subscriberId). Every message carries a durable dedup key, TTL-bound to cover the worst-case retry window. The public contract is “exactly-once effect, at-least-once attempt.”

Consequences

Higher storage cost for the dedup store, but bounded and predictable. Simpler retry logic on every downstream. No user-visible duplicates in normal or degraded operation.

11

How Fanout Systems Evolve

Fanout is not new — every distributed system has had to solve some version of it. What has changed is the scale, the latency expectations and the number of downstreams. Understanding the waves helps place your own platform.

1

Wave 1 — Single Broker (pre-2010)

ActiveMQ / RabbitMQ with topic-based routing. Great for internal event buses; wilts under celebrity fanout.

2

Wave 2 — Push Timelines (2010–2013)

Early Twitter, Facebook News Feed — fanout-on-write to materialised inboxes. Explodes at ten-million-follower accounts.

3

Wave 3 — Hybrid Fanout (2013–2018)

Push for the many, pull for the few, mixed at read time. Kafka becomes the shared log. Slack, WhatsApp and Instagram grow up here.

4

Wave 4 — Multi-Tenant, Multi-Sink (2018–2023)

Every message must land in inbox, search, analytics, moderation, audit and third-party webhooks. Sink isolation and fair-share scheduling become first-class.

5

Wave 5 — Edge Fanout & AI-Aware (2024+)

Delivery closer to the user with regional edges; presence-aware, personalised prioritisation; AI-generated summaries and translations produced as part of the fanout pipeline.

11.1 Adjacent Systems That Plug In

Search

Search Index

Consumes the fanout log as its input; guarantees search consistency with delivery.

Mod

Moderation Pipeline

Flagged content forks off the fanout with high priority to human review before broad delivery.

Push

Push Providers

APNs, FCM, WNS, WebPush; each with its own quirks, TTLs and rate limits.

Audit

Audit & Compliance

Every fanned-out copy is recorded with the producing policy version, retained per jurisdiction.

12

Key Takeaways

Fanout is a quiet, load-bearing layer. It never lands on a keynote slide, but it decides whether a chat feels “instant,” whether a feed feels “alive,” and whether a viral moment turns into an outage or into growth.

Key Takeaways

  • Fanout is a distribution problem, not a queueing problem. Its job is to answer “who, when, how” and delegate the byte-moving to specialised sinks.
  • The write-ahead log is the source of truth. Everything else is a materialised view; if the log is safe, the system can be rebuilt.
  • Choose a strategy per pair, not per system. On-write for small fanouts, on-read for the fat tail, hybrid for the middle.
  • Order per conversation, not globally. Document the guarantee precisely; global ordering promises will haunt you.
  • At-least-once on the wire, exactly-once in effect. Idempotency keys are the compound-interest asset of the whole platform.
  • Isolate hot keys early. Split, tier, sample — whatever it takes to stop celebrities from taxing every write.
  • Backpressure is a feature. Auto-scale on partition lag; never let retries take down neighbours.
  • Every downstream is untrusted. Circuit-breakers, per-sink quotas and a well-tended DLQ separate a resilient platform from a fragile one.
  • Fair-share scheduling. A single noisy tenant must not consume the whole delivery capacity of the fleet.
  • Shadow mode for every new sink and every new strategy. Real fanout traffic is the only honest load test.
i
Closing Thought

The best fanout systems are the ones nobody talks about. When they work, users think “of course my message showed up on all my devices, that’s obvious.” When they fail, the platform is on the front page. Every design choice — log, planner, workers, sinks, dedup — is in service of that quiet, load-bearing obviousness.