Drive the mail badge from pushed unread counts, not a 30s poll

Fixkamo-internal
Shipped
August 6, 2026 at 11:59 AM UTC
Author
kamo
Commit
328a0bf

MailButton ran setInterval(fetchUnreadCount, 30_000) in every open tab, and each tick called /api/email/folders — an endpoint that opens every folder in the mailbox to produce the one integer the badge needs. The email WebSocket was already connected but only reported that mail had arrived, so its handler's job was to schedule another fetch. EmailService now publishes the count itself on /topic/email/unread/<memberId>: a snapshot on connect, then one on every change, including \Seen set from another mail client — which the poll could only notice up to 30s late and the old WebSocket could not notice at all. useEmailWebSocket subscribes to that topic and exposes inboxUnread through EmailWebSocketContext. Snapshots carry the instant they were read and older ones are discarded, so a redelivery cannot resurrect a badge the member just cleared. inboxUnread stays null until the first snapshot lands, so "not known yet" never renders as a cleared badge. The REST path survives only as a cold fallback for a browser whose socket never came up: it fires when the socket reports it has given up, when no snapshot has arrived within the connect window, and — while disconnected only — on tab focus and on email:folder-changed. None of those is a timer.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing