Offline-First Sales Apps: Design for No Network
How to design an offline-first mobile sales app: local storage, a durable sync queue, idempotent APIs and conflict rules for field teams.
A sales representative standing in a shop at the edge of a district does not care about your API. They care that the order they just typed is still there when they press save. If the app needs a signal at that moment, sooner or later it loses an order, and after that the team goes back to paper.
Offline-first is not a feature you add at the end. It changes where the truth lives. This is the design I keep coming back to after building offline sales functionality for field teams.
What offline-first means
An offline-first app treats the network as optional. Every screen reads from local storage and every action succeeds locally. Synchronization happens in the background whenever a connection exists. The opposite, an online app with a cache, works until the first write without signal.
The device is the first database
Every write goes to local storage first and succeeds immediately. The server hears about it later. That means the app needs its own copy of everything a representative needs for the day: the route, the customers, the price list and the active offers. In Flutter, SQLite through sqflite or drift handles this well.
- Download the day's working set in one go, while the device is still on office Wi-Fi.
- Give every record a client-generated UUID so it can exist before the server knows about it.
- Show sync state on the record itself: pending, sent, or failed. People trust what they can see.
Use a sync queue, not a retry loop
Pending changes go into an ordered queue that survives the app being killed. When a connection appears, the queue drains in order. Each request carries an idempotency key, so a request that reached the server but lost its reply can be sent again without creating a duplicate order. I cover the server side of this in REST API design for mobile sales apps.
POST /api/v1/orders
Idempotency-Key: 7f3c1e52-device-41-000218
{ "client_id": "7f3c1e52", "customer": 1042, "lines": [...] }Decide conflicts before they happen
The price changed while the device was offline. The offer expired at noon and the order was taken at one. These are business questions, not technical ones, and the answers should come from the people who own the sales policy. Write the rules down, apply them on the server, and send the outcome back to the device in plain words.
Test it the way it will be used
Put the phone in airplane mode, take twenty orders, kill the app, restart the phone, then reconnect. If all twenty arrive exactly once, the design holds. Run this test before every release and again during rollout to the sales network.
The best compliment an offline app gets is that nobody notices when the network drops.
Common questions
What is an offline-first app?
An offline-first app stores data on the device and lets every action succeed locally, then synchronizes with the server in the background when a connection is available.
How do you avoid duplicate orders when syncing?
Give every record a client-generated ID and send an idempotency key with each request. The server stores the key and returns the original result if the same request arrives again.
Which local database should a Flutter offline app use?
SQLite is the usual choice, through the sqflite or drift packages. It handles relational sales data, survives app restarts and supports the queries a sales app needs.