Let's talk
concurrency

Proof of delivery app fails at the gate with no signal

A driver with no signal got a spinner, not a delivery form. Offline capture existed but could not be reached. What should work offline, and what should not.

· · updated

A delivery driver in hi-vis at a customer's door with a parcel, pressing the intercom

If your proof of delivery app fails at the customer’s gate with no signal, the usual cause is that saving offline was built but the screens leading to it still need the network. The driver cannot open the form, so nothing is recorded, and a dispute weeks later has no evidence. The first check is to put a phone in airplane mode and try to record a delivery from the very start.

Here is how that looked in our own delivery app, and what we decided should work offline.

A driver arrives at a customer’s gate with no signal, opens the app to record the delivery, and gets a spinner that never finishes. Weeks later there is a dispute about whether the goods arrived, and there is no proof, because nothing was recorded.

The part of the app that saves a delivery for later sending worked perfectly. It was unreachable. The screen for a single delivery fetched its details from the network, and there was no saved copy to show when the network was missing. “Offline delivery capture” had been built and could not be done offline.

What was actually going on

The saving-for-later part is what everyone thinks about, and it is the second half of the problem. The first half is whether the driver can open the form at all. An offline write is only useful if every screen on the way to it also works offline: the list, the detail, the pick-lists. Any one of them reaching for the network turns the feature into a demonstration.

Once the path worked, the harder question was which actions may be saved for later at all. When a saved action is finally sent, the server may already have received it, because the original request arrived and the reply was lost, or it may never have seen it. The system must end up right either way, which is only possible if it can tell which happened.

Creating a record with an identifier made by the phone is provable: send it again and the server either accepts it or rejects it as a duplicate of that exact identifier, which means it had landed. A change of state is different. Delivering, cancelling, marking an opportunity won: these carry no such identifier, and sending them again produces a conflict that only says the record is not in the state expected. It does not say why. You may have already applied it, or somebody at the desk may have moved it somewhere else.

What we changed

Each action was sorted on evidence, checked against the running system rather than assumed.

Saved for later, because they can be proved either way: recording a delivery, recording a failed delivery and logging an activity. Delivery and failure are final and reachable from only one earlier state, so a repeat produces one conflict for one reason, and treating it as “already done” is honest. They are also the actions done at a gate with no signal.

Online only: packing, dispatching, cancelling, and moving an opportunity through the pipeline. These are desk jobs done by someone with a connection, so needing one costs almost nothing. A cancel sent late, after somebody else moved the record, would look the same as one that had worked, and be reported as done when it was not. Marking an opportunity won can also create a draft order, which is a poor thing to send blindly.

The same pass added a saved copy of the single-delivery screen, so the driver can open it without signal. Every list and detail that gained a saved copy also gained a notice that what is on screen is the last successfully loaded version, because yesterday’s data shown as today’s is a worse failure than a screen that will not load.

Location and camera permission follow one principle. They can be denied, and none of them stops a delivery being recorded. A delivery that refuses to complete because a location fix was not obtained has decided that a coordinate matters more than the delivery.

What it did not fix

The proof photograph cannot be saved for later. It goes straight to storage using a short-lived permission that expires, and the image itself lives in a file on the phone that a saved request does not carry. A separate queue for photographs is possible and was not built; for now the photo is not offered offline.

What the screen does about it matters most. Before the driver confirms, the sheet says the photograph will not be sent because there is no connection, not afterwards and not in a log somewhere. A limit you have chosen not to build must be declared on the screen where the decision is made. Otherwise a driver believes the photo is waiting, and a dispute weeks later finds no evidence.

What should work offline in a delivery app

  1. Test the whole path in airplane mode, not just the final save: the list, the detail and the pick-lists too.
  2. For each thing your staff do in the field, ask what happens if it is sent twice.
  3. Allow offline only for actions that can be proved applied or not applied. Recording a delivery qualifies. Cancelling a job from the desk does not.
  4. Ask what the driver is told about anything that will not work offline, before they commit to it.
  5. Make sure a missing location fix or a denied permission never stops a delivery being recorded.

Where this ends up

The offline set in Sazinga Field is drawn on that test: records with a phone-made identifier are saved for later, most changes of state are not, and the screen says which before the driver commits to anything.

This came out of building Sazinga Field

Orders, stock, dispatch and the people on the road, in one place. The problem above is one we met while building it, and what we did about it is in the product.

If you run something like this, there is one thing you can do without a call: send one day's order sheet.