A notification raised on the wrong pod reached nobody

FixEmailService
Shipped
September 7, 2026 at 10:57 PM UTC
Author
Kamo
Commit
8471715

The broker here is enableSimpleBroker — in-heap, per pod, no relay — and this deployment runs two replicas. MemberNotificationService published straight into it with convertAndSend, so a notification only reached the member if the pod that happened to serve the raise was also the pod holding their WebSocket. Which one that was is a load-balancer decision, so roughly half of every member's notifications were published into an empty room. Nothing about it looks like a fault. The row is written, the publish returns cleanly, the STOMP session is healthy, and the next page load shows the notification sitting in the centre because the REST history read finds it. The only symptom is a bell that seems to lag reality, and only sometimes — which is why this survived: it is not reproducible on demand and it corrects itself on refresh. The mail path in this same service was fixed for exactly this and carries the warning: **************** explains that a relay ending in an in-heap convertAndSend must use an EPHEMERAL consumer, because every pod has to hear every message and a durable one admits only the first to bind. The notification topic was simply never given the same treatment. Frames now go to email.notify.<memberId> — under email.> because that is this service's own JetStream stream and publish() pins expectedStream — and NotificationWebSocketRelay puts them on each pod's topic. NATS only, never NATS plus a local send: this pod hears its own publish back through its own relay. The relay is its own component rather than a few lines in EmailWebSocketController because that controller's NATS lifecycle hangs off /app/email/subscribe, a MAILBOX subscription. A member who has never opened the mail app never sends it, and would have gone on receiving nothing. Also: GROWTH_HUB was in neither entitlement map, and requiredRight() answers null for anything absent — so it was shipping ungated by omission, which is the exact accident isDecided exists to catch. It belongs to no app as far as a right is concerned, and now says so.

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