An Android app can save data without a connection and still synchronize it incorrectly when connectivity returns. The difficult case is a lost response: the server accepted the operation, but the phone never received confirmation. Sending a new operation during retry can create the same business result twice.
The practical design is to persist a local change and an outbox record together, schedule delivery with WorkManager, and retain the same operation identifier across retries. Scheduling work on the device does not replace duplicate protection in your API.
When an outbox is worth the extra state
An outbox is useful when people should continue working through interruptions: recording study progress, editing a draft, or updating a task list. Once the local transaction succeeds, the interface can show the change with an honest pending state.
Do not label a queued request as synchronized. Also separate operations that can wait from decisions requiring an immediate server answer. A product that depends on current availability needs a clear pending or unavailable state rather than a silent promise of success.
Define the smallest useful record
Consider a hypothetical learning app saving the result of a practice session. Save the result and its outbox entry in one local transaction. If the process stops halfway through, both changes should be committed or neither should be committed.
| Field | Responsibility | Rule to establish |
|---|---|---|
| operation_id | Identifies one business action | Generate once and preserve during retry |
| entity_id | Identifies the practice session | Do not use a changing list position |
| payload_version | Identifies the message format | Support queued records during app upgrades |
| state | Tracks pending, sending, confirmed or blocked | Confirm only after an appropriate response |
| attempt_count | Supports diagnosis | Never use it to create a new operation identity |
Keep unnecessary personal information out of the queue. Store the fields the API actually needs, define who can access them, and establish when confirmed entries are removed.
Give the worker an explicit response policy
Android documents network constraints and retry backoff for WorkManager. A connected device can still face an unavailable API, an expired session or a permanent validation error. Those conditions need different outcomes.
The following pseudocode describes responsibilities rather than a complete Worker implementation:
read an unconfirmed operation
send its saved payload and operation_id
if the server confirms that operation:
mark it confirmed in a local transaction
if the failure is temporary:
retain the identity and retry with backoff
if the payload is permanently rejected:
mark it blocked and expose a recovery action
The server must coordinate duplicate detection with the business write. A separate check followed by an unprotected insert can still allow two concurrent requests through. Use a suitable transaction or uniqueness rule and return a consistent result for an already accepted operation.
For the learning example, distinguish a duplicate completion from an intentional new attempt. Reusing a session identifier for both would prevent a valid second attempt; generating a new identifier for every retry would fail to protect the first one.
Do not confuse conflicts with duplicate delivery
Two devices editing the same entity create a separate problem. Choose whether to merge changes, reject an outdated revision or ask the user to resolve a conflict. Repeated delivery of one operation does not establish which of two different edits should win.
Avoid making device clocks your only ordering authority. For some learning products, an immutable completed attempt is easier to reason about than repeatedly replacing a single progress object. Evaluate that choice against the actual product contract.
Test the points where ownership changes
- Disconnect after the user saves. The local result remains visible and pending.
- Let the server accept the request, then suppress its response. Retry produces one result.
- Stop the process during delivery. Reopening restores the pending queue.
- Expire authentication. Signing in again preserves the unsent operation.
- Upgrade while old payloads remain queued. They are delivered or converted deliberately.
Record the build, before-and-after states, operation identifier and response category. Exclude credentials and full user payloads from diagnostic logs. A screenshot of a success message is insufficient if the database contains duplicate completions.
For the wider release process, use the idea-to-release working checklist. For queued images, see the recoverable mobile upload design.
Frequently asked questions
Does unique work guarantee one server-side effect?
No. It helps coordinate work on a device. The API still needs a stable operation identity and duplicate-handling behavior.
When should an outbox entry be deleted?
After appropriate confirmation and according to a defined retention policy. Decide whether a short-lived confirmation record is needed for diagnosis before removing the evidence completely.
Primary sources
Check these original references again before making a release or policy decision.

