Delivery, batching & retries
How telemetry is buffered and shipped, what is retried, and how to flush before a process exits.
Buffered vs. immediate
| Call | Behaviour |
|---|---|
| Track an event, write a log line | Buffered. Appended to an in-memory queue and shipped in batches. Costs microseconds; never throws. |
| Capture an error | Sent right away. On Flutter it goes through the durable outbox, so it is retried until delivered — even across restarts. |
| Evaluate flags, fetch Remote Config, send a push, emit to a room | Immediate. You get the result back. |
Buffers flush on a timer (Flutter / JS 10 s, Python / .NET 5 s), when a batch is
full (maxBatch, default 50), and on shutdown:
| SDK | Automatic flush | Manual |
|---|---|---|
| Flutter | App backgrounded; the durable on-device outbox retries across restarts | await Nexus.instance.flush() |
| JavaScript | Page hide / tab switch; SIGTERM, SIGINT, beforeExit | await nexus.flush() |
| PHP | Laravel: after the response is sent (terminate) | sync by default; queue dispatchers available |
| Python | atexit; framework shutdown hooks | nexus.flush() / client.flush(timeout=2) |
| .NET | DisposeAsync; hosted service on host stop | await nexus.FlushAsync() |
Bounded memory
Server SDKs cap their buffer (max_queue / MaxQueue, default 10 000). When the
backend is unreachable for long, the newest items are dropped and counted
(client.Dropped in .NET) rather than growing memory until the process dies.
What is retried
Retries apply only where repeating is harmless: telemetry ingest, flag
evaluation, Remote Config fetches and reads — on network errors, 429 and 5xx,
with exponential backoff, jitter and Retry-After.
A push send, a voice leg, a realtime emit and a survey response are never
retried automatically: after a timeout you cannot know whether it happened, and a
duplicate notification or call is worse than a failure. 402 billing_suspended
and other 4xx answers are not retried either — the answer would not change.
Silent mode
Server SDKs throw typed errors from calls whose result you asked for. Set
silent=True (Python) / options.Silent = true (.NET) to get empty results and
a diagnostic instead. Background delivery is always silent.
Forking servers
Threads do not survive fork(). The Python SDK notices the PID change and
restarts its sender in each child (gunicorn, uWSGI, Celery prefork), so a client
built in the master process keeps working. PHP-FPM is request-scoped — journeys
and buffered telemetry are sent after the response.
