Design a newly-unread sender count
Model the difference between total unread messages and newly unread senders with Redis sorted sets and per-user read state.
Design: a newly-unread inbox indicator
A newly-unread badge tells someone how much new activity has arrived since they last opened an overview. It can clear when they open that screen even though they have not read every conversation. That makes it a separate feature from message read receipts.
We'll design a badge that counts distinct senders, preserve arrivals that race with opening the inbox, and keep reads correct while a background worker is behind. The example is our own small inbox service, not a claim about Slack's or another messenger's internals.
Decide what the number means
Bo sends Ada two messages and Cy sends one. Our badge shows 2 , because two sender accounts contributed new activity. It does not show three messages, two humans, or a count of unread conversations.
State What it means --- --- Newly-unread badge Distinct eligible senders after Ada's last inbox acknowledgement Thread unread A conversation contains messages beyond its own read position Message read receipt The client reported a particular message or position as read
Opening the overview advances only the first state. The wiki/chat-and-messaging-system-design chat design deals with delivery and thread-read reports separately. None of these reports proves that a person understood the text.
Repeated delivery of one send must not create another message. But two intentional sends with identical text are still two messages. Give each send an operation ID; use sender identity only when collapsing messages into the badge count.
For this design, only the recipient may view or acknowledge the inbox. Senders and recipients must be active accounts, and inactive senders disappear from badge reads. Blocking, group conversations and message deletion need additional rules; they are outside the executable example.
Clear what the screen observed
Deleting the whole badge set when Ada opens the inbox looks simple. It fails if a new message arrives after the server prepared her response but before the acknowledgement reaches the server. The deletion clears activity she never had a chance to see.
Give each accepted message a position within its recipient's inbox. Ada's first three messages occupy positions 1, 2 and 3. The response records that it observed through 3. Dee's later message receives position 4.
Store an acknowledged-through position A . A sender counts when their latest eligible message position is strictly greater than A. After acknowledging 3, Bo and Cy stop contributing, while Dee at 4 remains.
Positions describe acceptance order for one recipient. They are not wall-clock timestamps or a global order across all users. Equal timestamps cannot tell us which of two arrivals belonged to an earlier inbox response.
The server stores an issued snapshot containing its recipient and observed boundary. Ada acknowledges the snapshot ID, not an arbitrary position supplied by her client. The server checks ownership and updates A to the greater of its current value and the snapshot boundary.
That maximum handles multiple devices. If device B has already acknowledged through 4, device A's delayed acknowledgement through 3 leaves A at 4. A retry with the same operation ID returns its original receipt; that historical reply must not make the client lower a newer local boundary.