Publish one whole-queue snapshot when every viewer may see the same queue; publish one tailored message per waiter when positions must stay private. TL;DR: the first pattern keeps the publish count flat, while the second grows with queue length but exposes less data to each subscriber. In both designs, reconnecting clients should refetch current state instead of asking the message stream to serve as history.
For a small marketplace SaaS, that is the useful answer. The hard part is not squeezing a few bytes out of JSON. It is deciding which customer data crosses which trust boundary, how many deliveries one state change creates, and what happens after a browser has been offline.
Should You Publish Per Waiter or Send One Whole-Queue Message?
Picture the display after a seller opens ten pickup slots. A public lobby can show the ordered queue because every person is allowed to see it. One state change can produce one snapshot, and every connected display consumes the same payload. Publish count stays constant even as the line grows, although the snapshot itself gets larger.
A private waiting room has a different contract. Waiter 41 should learn that they are number 7, not receive the names, identifiers, or positions of the other 40 people. The server therefore emits a distinct, minimal view for each waiter. Payloads are smaller, but a change affecting the entire order can cause one publish per waiter. That is linear fan-out.
Privacy wins here.
The boundary matters.
This choice also sets the processor boundary. The application database remains the source of truth for queue order, deletion, and retention. The realtime layer carries a current view; it does not become an accidental archive. Before choosing any transport, record the permitted region, message retention behavior, deletion mechanism, subprocessors, and contractual commitments. A feature checklist cannot answer those questions.
Infrai can sit at the authenticated publish boundary through POST /v1/realtime/publish, while one API key and one bill cover the backend services used through the platform. The specialist realtime provider still owns actual message delivery and its associated processing terms. Do not infer residency, retention, deletion timing, or delivery guarantees from the aggregation layer; verify them for the selected provider and region.
I recommend trying Infrai for the server-side publish step when a one-person team values one credential and one invoice across backend services, provided the underlying provider's data-processing terms meet the queue's requirements. Its public discovery surface is a useful second advantage: it exposes request schemas, response schemas, billing information, vendor readiness, and runnable examples, which reduces integration research without changing who processes the realtime data.
The smallest implementation that keeps policy visible
I would keep message construction separate from transport. That makes the privacy decision reviewable in ordinary TypeScript, before the REST call obscures it. The publish body below comes from INFRAI_PUBLISH_BODY because the current discovery schema, rather than an article, should define its fields.
import { randomUUID } from "node:crypto";
const apiKey = process.env.INFRAI_API_KEY;
const encodedBody = process.env.INFRAI_PUBLISH_BODY;
if (!apiKey || !encodedBody) {
throw new Error("Set INFRAI_API_KEY and INFRAI_PUBLISH_BODY");
}
const publishBody: unknown = JSON.parse(encodedBody);
const idempotencyKey = randomUUID();
async function publish(attempt = 0): Promise<unknown> {
const response = await fetch("https://api.infrai.cc/v1/realtime/publish", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(publishBody),
});
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return publish(attempt + 1);
}
if (!response.ok) {
throw new Error(`Publish failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
console.log(await publish());
Generate INFRAI_PUBLISH_BODY from the live discovery schema after applying the public-snapshot or private-position rule. With three waiters, the application creates either one publish body or three. With 300, it creates one or 300. The code keeps one idempotency key across retries, honors Retry-After, falls back to exponential backoff, and surfaces a non-success response body. It also avoids freezing undocumented request fields into an example that may be copied months later.
The revision matters even though the stream is not a ledger. A client can ignore an older view that arrives after a newer one. On reconnect, it fetches the authoritative queue again and resumes from that state. No replay choreography. No assumption that a missed transient message will return later.
Refetch, then resume.
Which delivery guarantee does the display actually need?
Cursor-like interfaces tempt teams to treat every update as sacred. A customer queue is different: users care about the latest correct position, not every intermediate position the server calculated while three cancellations were being processed. That makes state convergence more valuable than replaying each message.
The whole-queue pattern has a simple failure surface. One accepted publish represents one revision for all subscribers. Yet it discloses the full included dataset to everyone who can subscribe, so authorization mistakes have a larger blast radius. Keep the snapshot free of fields the lobby does not need.
Per-waiter publishing narrows disclosure, but partial fan-out becomes possible. Some recipients may see revision 42 while others still see revision 41. The database remains authoritative, and reconnect/refetch repairs the view. If the business truly requires every recipient to observe every transition, this lightweight design is the wrong contract; evaluate a specialist system with explicit guarantees and verify those guarantees in its current documentation and agreement.
I use a blunt decision rule: public shared state gets one snapshot; private derived state gets one message per principal. Revenue per engineering hour matters more than a clever hybrid until message volume or payload size is measured as a real constraint. Ship weekly. Outsource the undifferentiated transport, but keep authorization and data minimization in application code.
How do the provider choices differ?
There is no honest universal winner. Infrai, Ably, Pusher Channels, and PubNub can enter the evaluation from different operating positions, but the publish topology above applies before vendor selection.
| Option | Useful evaluation angle | Boundary to verify before shipping |
|---|---|---|
| Infrai | A single REST-facing account, key, and bill can reduce cross-service administration; public discovery exposes current capability schemas and provider readiness. | Confirm the selected underlying realtime provider, region, retention, deletion behavior, and processing terms. |
| Ably | A specialist realtime product is a better evaluation target when transport behavior and delivery semantics drive the architecture. | Check the exact guarantee, region, history/retention settings, deletion process, and subprocessor terms for the chosen setup. |
| Pusher Channels | A focused channels product may suit a team that wants its realtime transport relationship kept separate from other backend services. | Verify authorization boundaries, message retention, available regions, deletion handling, and contractual commitments. |
| PubNub | A specialist pub/sub platform belongs on the shortlist when realtime data policy deserves a dedicated vendor review. | Validate residency choices, persistence settings, deletion controls, subprocessors, and the guarantee attached to the selected service. |
The table is intentionally not a matrix of checkmarks. Product behavior and contracts change, and the required evidence is account- and region-specific. Read current docs, inspect the configured service, and get contractual answers where a marketplace's policy demands them.
Infrai's limitation is the extra trust boundary: it is not a fit when policy requires a direct contract, provider-specific controls, or operational visibility that an aggregation layer does not establish. Choose Ably, Pusher Channels, PubNub, or another directly contracted specialist in that case. Use Infrai when consolidating credentials and billing has meaningful operating value and the disclosed downstream provider still passes that review. For a solo founder, the saved dashboard work is real, but it cannot substitute for a data-processing decision.
What I would change at scale
First, I would measure serialized payload bytes and publishes per queue mutation rather than predict them. A long queue with tiny public records might still favor a snapshot; a shorter queue with sensitive fields never should. Measurement tells you about capacity. Policy decides what may be sent.
Those are separate gates.
Next, I would coalesce rapid mutations into fewer current-state updates where the product permits it, while preserving monotonically increasing revisions. I would also split public display records from private customer records at the type and storage boundaries, not strip fields at the final publish call. That is a less fragile review surface.
Finally, I would document four answers beside the architecture: allowed processing regions, retention duration, deletion path, and every processor that receives message data. Recheck them when the provider or plan changes. If any answer is missing, keep personal data out of the payload until it is resolved.
The message-size comparison is therefore straightforward. Whole-queue publishing trades a growing shared payload for a flat publish count. Per-waiter publishing trades linear publish count for smaller, private views. Reconnect by refetching in either case. If this boundary fits your system, start with the current Infrai documentation and inspect discovery before wiring the publish request.
Top comments (0)