Telemetry that waits for the link to come back
Turn it on with one call and the SDK reports how the mesh behaved for your users: delivery latency, transport dwell, routing switches, relay activity, MLS handshake health. It batches on the device, holds the batches through an outage, and uploads them itself. Until you call it, nothing is collected.
Diagram: hosted telemetry, where a device running the protocol folds routing decisions, transport switches, and deliveries into batches, persists them while offline, and uploads them to the Offline Protocol ingest, which the developer portal shows as a dashboard.
unit-07 folds what the mesh did into batches, keeps them through the outage, and uploads them when the internet returns. The portal shows them as they land.
Why observability is hard past the edge of coverage
Most tooling assumes a live pipe. Agents on hosts stream metrics, logs, and traces to a backend in near real time. A drone mid-mission or a phone in a blackout produces its most useful events exactly when nothing can stream.
The SDK keeps the record on the device. Events are folded into batches the moment they happen and written to sealed storage. A device that spends three days off-link comes back with its three days intact.
Upload happens when a link exists. The uploader wakes when the internet transport comes up, sends up to eight batches, and goes back to sleep. Your app makes no lifecycle call and runs no agent.
The thirteen event types
The inventory is fixed. Each type carries exactly the fields below and nothing else, and a fixture in the SDK repository pins the bytes. Every other event the engine produces is dropped by name before its payload is read.
Inside a minute rollup
The rollup is the row most dashboards are built on. The SDK still samples a metrics frame every 5 seconds, but the frames never leave the device. Once a minute they are folded into one row that says what the device was connected through, who was nearby, what was waiting to go out, and whether it was carrying traffic for others.
Illustration: one minute rollup from unit-07 as a small dashboard. Transport at the end of the minute WiFi Direct, five neighbours on average, relaying for 41 seconds, two pending acknowledgments, time split across BLE, WiFi Direct, and no transport, and 18 sends attempted with 17 succeeded.
In code
// Nothing is collected until this call. Key and app id come from the developer portal.
await protocol.start();
await protocol.enableTelemetry({
apiKey: 'mp_…',
appId: 'app_…',
appVersion: '1.4.2',
});
// Later: what has been buffered, sent, accepted, and dropped on this device.
const stats = await protocol.telemetryStats(); The native module owns the lifecycle. Going to the background closes the session and flushes it, and the next foreground opens a new one. The same call exists in Python and Rust.
How the pipe behaves
Before enableTelemetry succeeds the SDK collects, buffers, persists, and uploads nothing. An app that never calls it ships no telemetry.
Batches are sealed with the per-install key and survive restarts. A batch waits up to six days for a link, then is dropped and counted.
No event has a field for a message id, peer, group, or name. The one id the pipe reads, for handshake latency, is hashed in memory and never uploaded.
The service bills accepted events. The device keeps its own count of sent, accepted, and dropped, so the invoice can be reconciled against it.
Where it applies
- Autonomous fleets
- Post-mission analysis from batches captured while off-link and uploaded on return.
- Industrial sites
- Site operators get a record of what the mesh did through the outage.
- Public sector
- Humanitarian programs keep an operational record with no personal identifiers in it.
- What it observes
- The DORS transport, delivery, relay, and MLS session layers. Leaf nodes have no pipe of their own yet.
Hosted telemetry FAQ
Does telemetry change the privacy model?
No. Nothing is collected, buffered, or uploaded until your app calls enableTelemetry with a key from the developer portal. The event inventory is fixed and has no field for a message id, peer address, group id, username, file name, or message content, and a fixture in the SDK pins the exact bytes that leave the device.
What gets uploaded?
Fourteen event types: message delivered, failed, deferred, and relayed; relay promoted, demoted, and demoted on the battery floor; routing decisions; transport state changes; device capability changes; two MLS failure signals; a metrics rollup once a minute; and a session summary at each session boundary. Each carries only the numeric or token fields listed in the docs.
Where does it go?
To one Offline Protocol ingest endpoint fixed at build time, over TLS. Since SDK 0.30.0 the uploader trusts only the roots of the four certificate authorities the ingest's certificate can come from, compiled into the SDK, and never the device's trust store. The uploader follows no redirects, so that endpoint is the only place a batch can be sent. The developer portal turns the batches into dashboards and cross-app benchmarks.
Should we update the SDK if we use telemetry?
Yes, if your app calls enableTelemetry on SDK 0.26.0 to 0.29.0. Those versions trusted the whole bundled Mozilla root set, which still included an eMudhra root withdrawn after the October 2026 key compromise, so someone holding one of those keys on the network path could have read the telemetry API key and the batches. SDK 0.30.0 trusts only the roots the ingest uses. Apps that never enable telemetry are not affected.
Can we still read events in our own code?
Yes. onEvent still hands every protocol event to your app as it happens, with no telemetry enabled and no key. getTransportMetrics and getBleDiagnostics answer the same way. What the free API does not carry is the minute rollup or the session summary; those exist only in the hosted pipe.
What happens when the device is offline?
Events are cut into batches and persisted on the device, sealed with the same per-install key as the outbox. The queue holds up to 64 batches or 4 MiB and survives restarts. The uploader wakes when the internet transport comes back, and a batch older than six days is dropped unsent because the ingest deduplicates for seven.
What is in the minute rollup?
One row per minute, folded on the device from the metrics frames: neighbour count average, max, and median; pending acknowledgments; retry queue depth; how many milliseconds were spent on BLE, WiFi Direct, the internet, or nothing; how long the device relayed; the transport at the end of the minute; and sends attempted, succeeded, and failed.
How are identifiers handled?
No event has a field for one. The pipe reads a single identifier, the peer id on MLS records, only to pair the start and end of a handshake and compute a latency, and with scrubIds on by default it is hashed in memory first. Setting includeDeviceId stamps an opaque per-install id on each batch so the portal counts devices instead of sessions, and the privacy docs spell out the store disclosures that choice adds.
When does a batch leave the device?
Every 30 seconds when there is something to send, when the buffer passes 256 KiB, when the app goes to the background, when the internet comes back, and on flushTelemetry, endTelemetrySession, or disableTelemetry. Below 15 percent battery and not charging, the uploader defers everything except the background and explicit flushes.
How is it billed?
The service meters accepted events: 250,000 a month on Free, 2 million on Pro, and 10 million on Scale, then $0.05 per 1,000. telemetryStats() reports buffered, sent, accepted, and dropped counts on the device, so an invoice can be checked against what the SDK itself counted. setTelemetryEnabled(false) stops collection without shutting the uploader down, and disableTelemetry stops it with a final flush.
How do device events reach our own backend or Kafka?
Hosted telemetry sends the SDK's runtime events to Offline Protocol's ingest, and onEvent hands every protocol event to your own code as it happens. For your own records, such as readings and workflow events, the docs describe a retained-event pattern: the device commits each event durably, a collector accepts it over the mesh, and a destination adapter you build submits it to your backend and records the backend's acceptance. For Kafka, a Confluent source connector is in private evaluation and exports retained collector events through Kafka Connect; contact sales for access. Read the backend delivery guide.

