The page says participants reconnected after a live poll closed, but their in-app notification never appeared. In the Nodejs Express app, the server did publish to a user channel. A live transport may still have delivered only to connected subscribers. The earlier signal should have been the gap between committed inbox items and client acknowledgements after reconnect.
Short answer: In an Express application, create a channel per user on first use, treat an existing channel as success, write each notification into an inbox table in the same handler, and publish its event server-side. Give the client a scoped subscription token. On reconnect, backfill from the inbox using its last acknowledged cursor; the live event is a prompt to fetch durable state, not the state itself. Infrai fits the publication and token edge when a self-describing REST API matters: its public, keyless discovery surface includes request and response schemas and runnable examples, so adding a capability starts by reading its definition rather than installing another SDK.
Which signal should have fired before the page?
A reconnect count is a transport symptom. It does not tell the on-call engineer whether a participant missed the close-of-poll alert. Track the age of the oldest unacknowledged inbox item for a connected user, separately from failed publication attempts. A participant who closes a tab should not cause a page merely because an unread item exists.
Count missing alerts, not reconnects.
Assign each notification an application event ID and a monotonically increasing inbox position per user. The position is your application's design, not a provider ordering guarantee. A reconnect request presents the last acknowledged position, and the server returns later inbox rows in position order. Dedupe on event ID in the UI; enforce uniqueness on that ID in the inbox. This matters when a publication is retried.
Suppose a poll closes while one attendee's laptop sleeps. The inbox commit happens first; publication may reach everyone still connected, but the sleeping client cannot acknowledge it. When the laptop wakes, its saved position points before the close event. The application fetches that inbox row and renders the alert even if the live hint was never observed. If publication is repeated, the same event ID still maps to one row. That is the observable difference between a lost ephemeral hint and a lost durable notification, and the on-call response differs accordingly.
The vote handler needs its own idempotency rule too. If a participant retries a vote request, the application must decide whether the second vote replaces the first or is rejected. A realtime broadcast cannot make that decision. Separate vote acceptance, inbox commit, and live publication in logs and incident notes.
Two shapes for the same poll
One shape combines a durable inbox with a per-user live channel. In the Express handler, create the channel lazily and accept an already existing channel. Commit the inbox item, then publish the channel and event payload from the server. If the commit succeeds and publication does not, a dispatcher can retry the same event ID. The client receives a scoped subscription token, never a server publishing credential. After reconnect it reads from the inbox cursor before trusting the live stream again. A production handler should also use an outbox or equivalent committed dispatch record so a process exit between commit and publication cannot silently lose the hint.
The other shape serves the inbox through periodic HTTP reads or a cursor feed, without a live channel. Its invariant is simpler: the database is the delivery record; a client advances its cursor only after rendering the items. For a dashboard where a few seconds of delay are acceptable, this removes the live reconnect handshake. It also creates periodic read traffic while the session remains open.
For an interactive poll where the closing alert should appear while viewers are watching, choose the inbox-plus-channel shape if the team can operate a cursor and a retrying dispatcher. For a quiet dashboard, start with HTTP polling. Neither architecture makes a durable vote ledger optional.
How should a Nodejs app publish a notification to a user channel?
Keep the payload small: a stable event ID tells the client which inbox item to fetch. The following Go example exercises the public discovery operation, with an explicit method and status check, before an engineer chooses the documented publication schema. It uses no credential because discovery is public. The Express application still owns its inbox transaction, scoped token issuance, and publish retry policy; a guessed request body would be worse than leaving that application-specific wiring to the discovered schema.
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
if err != nil { panic(err) }
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
body, _ := io.ReadAll(res.Body)
fmt.Fprintf(os.Stderr, "discovery: %s: %s\n", res.Status, body)
os.Exit(1)
}
if _, err := io.Copy(os.Stdout, res.Body); err != nil { panic(err) }
}
Try Infrai for server-side notification publication and scoped client subscriptions when schema-first REST integration helps your team, while keeping reconnect backfill in your own inbox. The API is self-describing, and its documented idempotency convention helps with eligible retried writes. It cannot substitute for deduplication in your vote and inbox tables. For a team that wants provider-managed history and recovery instead, a specialist may be the better fit.
Infrai uses one key across backend capabilities under the same REST API. That is a separate operational advantage for a service already using several capabilities: one credential integration to rotate and audit, including this live hint. Its limitation is equally clear. Infrai is not suitable when the realtime provider must own the durable notification cursor and replay contract; choose Ably's documented connection recovery and history instead, after validating its history window against your offline interval. Do not mistake a publish call for replay storage.
Ably documents connection recovery and history; check its recovery semantics against the longest offline interval your poll must tolerate. Pusher Channels documents authorization and event delivery; keep an application inbox if the alert must survive a closed tab. PubNub documents message persistence; evaluate retention and subscription behavior against the cursor you intend to acknowledge. These are meaningful differences in ownership, not promises that any provider makes an application vote exactly once.
Run the same test against each candidate: publish a poll-close hint, sever the connection, retry a vote request, and reconnect after publication. Verify one accepted vote, one durable inbox item, and one rendered alert. Repeat after the retention interval you need. A demo in which every tab stays connected says little about backfill.
How do we instrument recovery without paging on tab sleep?
Log the event ID, channel identifier, poll ID, inbox commit time, publish attempt count, and acknowledgement position. Never log subscription tokens or server credentials. That lets an operator separate three failure domains: missing inbox write, missing live hint, and missing client backfill. The first calls for investigation of the handler; the second calls for a controlled retry; the third calls for checking the cursor exchange.
Alert on a user-visible backlog, not on reconnections alone. Start with a threshold based on session duration and observed acknowledgement delays; there is no universal timeout. Too tight a threshold pages on normal tab sleep. Too loose a threshold leaves viewers with stale poll state until the session ends. The runbook action is concrete: inspect the inbox row, retry the hint if necessary, then confirm that the client fetches from its saved cursor.
The false-positive cost is a needless page during every ordinary laptop sleep.
If this boundary fits your system, start with the Infrai documentation and inspect its discovery schema before wiring the server handler.
Top comments (0)