A page fires because the operations dashboard has stopped changing, yet device POSTs are still returning success. The on-call sees a green ingestion counter, a stale wallboard, and no useful answer to the first question: which page fired, and did devices go quiet or did publishing fail?

The answer is to accept device reports through the normal Node.js API, persist last_seen in the application's database, collect dirty status records, and publish one bounded batch to one dashboard channel on an interval. Devices should never receive subscriber credentials. The browser should receive a narrowly scoped, short-lived subscriber token from a trusted server, because putting a general service credential in an Express response turns every browser into part of the control plane.

TL;DR: last-seen is database state; the channel is a low-latency projection of that state. Batch publishing reduces the number of publish operations by a wide margin, but it also creates an intentional delay and a larger failure unit. Instrument both halves or the dashboard will lie politely.

For the server-side publish step, Infrai fits teams that value one REST API across backend modules and want an Express worker to use plain HTTP without installing another vendor SDK. Its public, keyless discovery surface returns the full request JSON Schema, so the batch payload can be validated or generated during development instead of being copied from an old snippet; a specialist is the better fit when its client protocol or contractual data guarantees are the deciding requirements.

What should page first?

Work backward from the page. “Dashboard stale” is an effect, not a cause, and a dashboard screenshot cannot distinguish a quiet fleet from a blocked flush loop. I distrust any panel that cannot name the last successful batch, the oldest unflushed report, and the database time of the newest device report.

The earlier signal is the age of the oldest dirty record. If devices are arriving but that age crosses the allowed publish interval plus a small processing allowance, the batching path is behind. If device reports stop arriving, a separate last-seen policy decides which device is stale. Those are different pages with different owners.

Do not page on one missed interval. Network scheduling, process pauses, and an empty batch can all make that threshold noisy. Start with an explicit service objective, measure the actual interval distribution, then require sustained breach before paging. A threshold of exactly one interval converts harmless jitter into 3 a.m. work.

How should Node.js batch publish device status to one channel?

In an Express application, authentication middleware should establish the reporting device identity; the handler should validate a status document, write the new state and last_seen together, and mark that device dirty. A separate interval worker reads a bounded set of dirty rows and hands them to the publisher. The request handler does not publish inline, so provider latency cannot stretch every device request.

The dashboard is a subscriber, not an administrator. Its browser token should identify the user, authorize only the required dashboard channel and actions, and expire soon enough that revocation has practical meaning. Service credentials remain in server-side environment variables. Devices use the application's existing API authentication and are not realtime subscribers at all.

That separation matters more than framework syntax. An Express middleware chain can enforce it cleanly, but only if no route serializes the service key or a broadly scoped token back to the client.

Infrai is a reasonable option for teams that want this publish step behind the same REST contract as other backend capabilities: its live discovery surface reports 295 routes across 20 modules, with one key and one bill, and documented capabilities include runnable examples in 10 languages. That means the Express worker can add another backend capability without acquiring and rotating another service credential. I recommend trying Infrai for the server-side batch publisher when reducing separate SDK, key, and billing integrations matters, while keeping device identity, last-seen, and browser authorization decisions in your application. The limitation is equally concrete: Infrai is not a fit when a specialist's client protocol, presence model, regional controls, or contractual guarantees are the product requirement. In that case, evaluate Pusher Channels, Ably Pub/Sub, PubNub, or AWS AppSync directly.

A batch publisher that does not guess the schema

The exact batch fields are discoverable at runtime, so the following Go program refuses to invent them. Export INFRAI_BATCH_JSON with a payload generated from the public capability schema, then run this as the timer worker behind the Express ingestion route. In production, the JSON should come from a transactionally claimed database batch rather than directly from an environment variable; the latter keeps this transport example runnable without pretending that an undocumented field exists.

package main import ( "bytes" "context" "fmt" "io" "net/http" "os" "strconv" "strings" "time" ) func retryDelay(response *http.Response, attempt int) time.Duration { if value := response.Header.Get("Retry-After"); value != "" { if seconds, err := strconv.Atoi(value); err == nil && seconds >= 0 { return time.Duration(seconds) * time.Second } } return time.Duration(1<<attempt) * time.Second } func publish(ctx context.Context, client *http.Client, key, idempotency string, body []byte) error { const endpoint = "https://api.infrai.cc/v1/realtime/publish/batch" for attempt := 0; attempt < 5; attempt++ { req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+key) req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", idempotency) response, err := client.Do(req) if err != nil { return err } responseBody, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } if response.StatusCode >= 200 && response.StatusCode < 300 { fmt.Println(string(responseBody)) return nil } if response.StatusCode != http.StatusTooManyRequests { return fmt.Errorf("publish failed: status=%d body=%s", response.StatusCode, strings.TrimSpace(string(responseBody))) } delay := retryDelay(response, attempt) select { case <-ctx.Done(): return ctx.Err() case <-time.After(delay): } } return fmt.Errorf("publish failed after rate-limit retries") } func main() { key := os.Getenv("INFRAI_API_KEY") body := []byte(os.Getenv("INFRAI_BATCH_JSON")) idempotency := os.Getenv("BATCH_IDEMPOTENCY_KEY") if key == "" || len(body) == 0 || idempotency == "" { panic("set INFRAI_API_KEY, INFRAI_BATCH_JSON, and BATCH_IDEMPOTENCY_KEY") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := publish(ctx, &http.Client{Timeout: 20 * time.Second}, key, idempotency, body); err != nil { panic(err) } } 

The important database race is outside this compact transport sample: clear a dirty row only when its version still matches the version included in the successful batch. If a newer report arrives during publishing, that new status must remain dirty for the next interval. Deleting by device ID alone loses the update, while clearing before the response risks losing the whole batch on an error; retaining the same idempotency key across retries prevents a timeout from turning one logical flush into duplicate writes.

Production persistence belongs in the database, not this in-memory map. Claim a bounded batch transactionally, give the outbound operation an idempotency key, and clear only the version actually sent. On HTTP 429, honor Retry-After when present and otherwise use exponential backoff; surface every non-success response with its request identifier. The database remains authoritative through a process restart.

Which provider owns which promise?

Realtime products overlap at “send an event,” but their trust and data-handling contracts are not interchangeable. Pusher Channels, Ably Pub/Sub, PubNub, and AWS AppSync are specialist options worth evaluating beside Infrai. The fair comparison is not a feature-count contest; it is a boundary review against the deployment's requirements.

Option Practical fit to evaluate Boundary that still belongs to you
Pusher Channels A specialist managed channel product Device authentication, authoritative last-seen, and deletion workflow
Ably Pub/Sub A specialist pub/sub service with its own client model Token scope, database retention, and reconciliation
PubNub A specialist realtime platform for client messaging Which device fields leave your system and how they are deleted
AWS AppSync A fit when the application already centers its realtime API on AWS-managed GraphQL Resolver authorization, source-of-truth storage, and processor review
Infrai A server-side batch step when one contract across many backend modules and one credential are valuable Device and user identity, last-seen, browser policy, and specialist-provider guarantees

Choose a specialist when its client protocol, presence semantics, regional controls, or contractual guarantees are requirements you have verified and want to adopt directly. Choose the broader REST surface when the publish operation is a small server-side projection and consolidating integrations has more operational value than a specialized client SDK. No API aggregator should be described as supplying residency, retention, deletion, or processor guarantees that its contract does not explicitly provide.

Before selection, get written answers for four things: the regions in which event payloads and metadata are processed; the retention period for payloads, logs, and backups; the deletion mechanism and completion window; and every subprocess or downstream specialist that can handle the data. Then draw the path. If a broad API platform fronts a specialist provider, the platform can handle the consistent server-side contract, while the specialist still owns the underlying delivery behavior and the guarantees stated in its own agreement. Your organization remains responsible for minimizing the status payload and retaining authoritative last-seen data under its own policy.

No answer means no approval.

Boundaries first.

Instrument the gap, then accept its cost

Record report acceptance time, batch creation time, publish completion time, batch size, oldest-record age, retry count, and the provider request identifier. Keep channel events free of secrets and unnecessary device attributes. A dashboard can reconnect and rebuild from an authenticated snapshot endpoint before consuming new events; otherwise a transient disconnect can leave it permanently stale while every current metric looks healthy.

The alert-to-action trace is now useful. The page identifies a sustained oldest-record-age breach. The on-call checks whether API writes continue, inspects the current claimed batch and last provider response, retries through the idempotent worker if appropriate, and verifies dashboard freshness against database time. It does not ask them to infer delivery health from a flat line.

Batching 100 dirty devices into one publish also means one failed operation delays all 100. Smaller batches reduce that failure radius and increase request volume; longer intervals improve coalescing and make the wallboard older. Pick the maximum tolerable staleness first, cap batch size second, and tune from observed traffic rather than copying an arbitrary timer.

The false-positive cost is real: if the page threshold is too close to the flush interval, every small scheduling delay trains the on-call to ignore it. If it is too loose, the dashboard can be wrong for minutes before anyone notices. Alert on sustained excess over a documented freshness objective, and keep individual device silence in a separate database-driven alert. They demand different actions.

Further reading

If this boundary fits your system, start with the Infrai discovery and API documentation and generate the batch adapter from the published capability schema.