Skip to content
ub3r.io

IP Messaging Protocols · Chapter 2 · 12 min

Channels, threads and receipts

Model conversations as channels with per-member cursors, and send receipts that scale.

Channels, membership and cursors

A channel is an ordered log of messages plus a set of members who have access to that log. Direct messages are essentially channels with two members, while group chats can involve hundreds or thousands of participants. Threads are child channels anchored to a parent message, providing a way to organize sub-conversations within a larger context. Keeping a single primitive — the ordered log — simplifies your storage model and synchronization algorithms. This unified approach makes your system easier to maintain and extend as your application grows in complexity.

Playground· Channels and receipts with mx.chat
output

Waiting for output…

Storing a "read" status for every individual message and every user is a recipe for database explosion. In a channel with 500 members and 10,000 messages, you would need five million rows to track every read event. Instead, you should store one read cursor per member, representing the sequence number of the last message they have successfully read. This allows you to determine the read status of any message with a simple comparison. Cursors are an elegant solution to a complex scaling problem that affects every messaging system.

typescript
interface ChannelMember {
  userId: string;
  readCursor: number;
  deliveredCursor: number;
}
const isRead = (member: ChannelMember, msgSeq: number) => {
  return member.readCursor >= msgSeq;
};

Delivery receipts and batching

Delivered receipts work in a similar way to read receipts, using a delivery cursor to track the furthest message received by each member's device. This information is crucial for implementing features like message fallback to SMS or providing visual feedback to the sender. By separating delivery and read status, you give users a clearer understanding of the state of their communications. This transparency is a key feature of the ub3r messaging platform and one that users have come to expect.

Implementing delivery receipts at scale requires a robust and scalable architecture. You must ensure that receipt updates are processed quickly and efficiently, without putting excessive load on your database. By using asynchronous processing and intelligent caching, you can maintain high performance even during periods of heavy usage. This level of technical sophistication is what allows your messaging system to remain reliable under pressure.

Scaling and future-proofing

As group sizes grow, the overhead of managing cursors for every member becomes more significant. You may need to implement more advanced strategies, such as only tracking cursors for active members or using a distributed cache for frequently updated cursors. Scaling a messaging system requires constant attention to these small but impactful details of data management. The goal is to maintain high performance and low latency even as your system grows to support millions of active users.

When designing your channel and member models, consider future requirements like message reactions, edits, and deletions. Each of these features adds complexity to your ordered log and your cursor management logic. By starting with a clean, extensible foundation, you can incorporate these advanced features without compromising the performance or reliability of your system. A well-designed data model is the secret to long-term architectural success and ease of maintenance.

Discussion (4)

ML
Marcus Lindqvist · 2026-10-02

Would love a follow-up on how this behaves under carrier throttling. We see very different numbers per route.

LF
Lena Fischer · 2026-10-12

Same experience here. Per-route metrics were the only way we found it.

PR
Priya Raman · 2026-10-03

Ran the playground with three concurrent sends — receipts came back out of order, exactly as the text says. Great way to make it stick.

TH
Tomás Herrera · 2026-10-04

We learnt the idempotency lesson the expensive way: duplicate OTPs during an incident. This chapter is now required reading on my team.