Record first, send later
The first rule is that a reading or event is written to local storage before anything tries to send it. If the app is closed, the device restarts or the battery dies, the record is still there. This is the same idea as an outbox in messaging: the network is something the data waits for, not something it depends on to exist.
Give each record two things when it is created:
- A stable identifier, generated on the device, so that every later step can refer to the same record and the backend can recognise a copy it has already stored.
- The time it was created, from the device clock, plus whatever the backend needs to make sense of that time later, since device clocks drift and can be wrong after a long time offline.
Bound the buffer
Storage on a device is finite, so every collection design needs a limit and a rule for what happens at the limit. Industrial data loggers make the choice explicit. Campbell Scientific’s loggers, for example, store tables as ring memory by default, where the newest records overwrite the oldest, or can be set to fill and stop, keeping the oldest records and storing nothing more until the table is reset.
Software buffers make the same choice with age and size limits. OpenTelemetry’s disk-buffering module for Java, for instance, has a maximum folder size and a maximum age for reading a file, after which the file is treated as stale and removed. Whichever rule you choose, count what is dropped, so a gap in the data can be told apart from a quiet period.
Wait for a path
When the device itself will get a connection back, the question becomes when to send. Mobile operating systems provide schedulers for exactly this, so the app does not have to keep running:
- Android’s WorkManager runs work that persists across app restarts and device reboots. Work can carry constraints, such as running only on an unmetered network, and failed work can be retried with a configurable exponential backoff policy.
- On Apple platforms, a background processing task request has a
requiresNetworkConnectivityflag that marks the task as needing network connectivity, which is what an upload needs.
Send in batches rather than one record at a time, and keep each batch small enough to finish within a short connection window.
Use a local hop when there is no internet at all
Some devices never get their own connection: a sensor in a basement, a meter on a remote site, a phone in a team working beyond coverage. Their data has to cross a local link first.
- A gateway or collector nearby receives data over a short-range link and forwards it when it has a backhaul.
- A local broker can keep data for clients that come and go. In MQTT 5.0, a broker keeps session state for a disconnected client, including messages sent with quality of service 1 or 2 that are waiting to be delivered, until the session expiry interval passes.
- A carrier, such as a phone or vehicle that visits the site, can pick data up and deliver it later. This is data muling.
Each hop is a store-and-forward step, described in how store-and-forward messaging works. The pattern scales to extreme cases: NASA describes delay/disruption tolerant networking, which stores data at each node until the next one is available, and its PACE mission uses it operationally for housekeeping telemetry.
Confirm before you delete
The device should keep a record until the destination has confirmed it, and “sent” is not the same as “stored”. Offline Protocol’s backend delivery guide makes this a rule: do not clear the source merely because a send call returned or the SDK reported delivery, but decide which acceptance transfers responsibility, and correlate the backend’s own durable acceptance with the event identifier. The difference between these stages is covered in delivered, accepted, committed.
Because records will sometimes be sent twice, after a lost acknowledgment or a restart mid-upload, the backend should write them in a way that makes a repeat harmless, using the stable identifier as the key. That property is idempotency.
A checklist
- Write every record durably before sending it, with a stable identifier and a timestamp.
- Set a storage limit, choose what to drop at the limit, and count drops.
- Let the operating system schedule uploads when a network is available, in batches, with backoff.
- Where devices have no connection, add a local hop through a gateway, broker or carrier.
- Delete a record only after the destination confirms it, and make repeated writes harmless.