Back to BlogIndustry Guides

A Delivery Tracking Platform: Orders, Drivers, Statuses

Rupak Amin

Founder & Lead Engineer, RAITHub

9 min read

RAITHub ships and tests production software. See QA as a Service or talk to us.

A delivery tracking platform is a state machine with a map on top. Each delivery moves through defined statuses (created, assigned, picked up, in transit, delivered or failed), a driver is assigned, location updates stream in, and the customer sees an honest status and ETA. Build the status model so illegal jumps are impossible, handle location updates that arrive out of order, and test that no delivery gets stuck.

This is engineering guidance. If you would rather have it built and tested for you, see how RAITHub would build this below.

Why is delivery tracking a state machine, not a status field?

Because a delivery has an order to its stages, and skipping or reversing them is a bug, not a feature. "Delivered" must never appear before "picked up"; a "failed" delivery must not quietly become "in transit." A free-text status field lets any of that happen. A state machine defines the allowed transitions, so the only way to reach "delivered" is through the legal path, and every change is recorded. That is also what makes the customer-facing tracking honest: the page shows a real state, not a guess.

ApproachProblemState machine instead
Free-text statusImpossible states appear (delivered before pickup)Only legal transitions are allowed
Status set from the app onlyA dropped network call loses a status changeStatus changes are events, idempotent and recoverable
Latest location winsA late GPS ping overwrites a newer one; the pin jumps backwardsLocation updates keyed by timestamp; stale ones ignored
ETA guessed in the UIEach client computes a different ETAOne ETA computed server-side from the real state

What does the data model look like?

A delivery has a current state and a driver; status changes and location updates are events, so you keep the history and can replay it.

CREATE TABLE deliveries (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  order_id    bigint NOT NULL,
  driver_id   bigint REFERENCES drivers(id),       -- null until assigned
  state       text NOT NULL DEFAULT 'created',
              -- created | assigned | picked_up | in_transit | delivered | failed | cancelled
  state_since timestamptz NOT NULL DEFAULT now(),  -- for the stuck-delivery check
  created_at  timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE delivery_events (
  id          bigserial PRIMARY KEY,
  delivery_id uuid NOT NULL REFERENCES deliveries(id),
  to_state    text NOT NULL,
  by_driver   bigint,
  recorded_at timestamptz NOT NULL,                -- when it happened on the device
  created_at  timestamptz NOT NULL DEFAULT now()   -- when the server received it
);

CREATE TABLE location_pings (
  delivery_id uuid NOT NULL REFERENCES deliveries(id),
  lat         double precision NOT NULL,
  lng         double precision NOT NULL,
  pinged_at   timestamptz NOT NULL,                -- device time; used to order pings
  PRIMARY KEY (delivery_id, pinged_at)
);

Keeping recorded_at (device time) separate from created_at (server time) lets you order events correctly even when a driver's phone was offline and synced late, which it will be.

How do you handle location updates that arrive late and out of order?

Drivers lose signal, so pings arrive in bursts, out of order, sometimes minutes late. Order them by the device timestamp, not arrival time, and ignore a ping older than the latest one you already have, so the map pin never jumps backwards. Do not store every ping forever at full resolution: keep enough to show the route and the current position, and thin the history. The customer-facing position should read the latest valid ping, and the ETA should be computed server-side from the real state and position, so every client shows the same number.

How does driver assignment work?

Assignment is the decision of which driver takes which delivery, and like warehouse allocation it needs a rule and must be atomic. A driver should not be double-booked, and a delivery should not be assigned to two drivers. Assign inside a transaction that checks the driver is available and marks the delivery assigned in one step, so two dispatchers (or an auto-assigner running twice) cannot both claim the same driver. The rule can be nearest-available, fewest-active-jobs, or zone-based; keep it a pure function of the current state so it is testable. This is the same scheduling and assignment concern as field-service work, covered in a field-service scheduling SaaS.

How do you stop a delivery getting stuck?

With a scheduled check, the same overdue pattern used for SLAs and workflows: each state has an expected maximum time, and a job finds deliveries sitting too long and surfaces them to dispatch. A delivery stuck in "picked up" for hours is either a lost driver or a lost status update, and either way a human needs to see it.

-- Deliveries sitting too long in their current state (the dispatch worklist).
SELECT id, driver_id, state, state_since
FROM deliveries
WHERE state NOT IN ('delivered', 'failed', 'cancelled')
  AND state_since < now() - make_interval(mins => $1);  -- max time for this state

How do you test a delivery tracking platform?

Test that illegal transitions are refused, that stale pings are ignored, and that assignment is atomic.

import { describe, it, expect } from 'vitest'
import { transition, recordPing, latestPosition, assign } from './delivery'

describe('delivery tracking', () => {
  it('refuses an illegal status jump', async () => {
    const d = await seedDelivery({ state: 'created' })
    await expect(transition(d.id, 'delivered')).rejects.toThrow()  // must go through pickup/transit
  })

  it('ignores a location ping older than the latest', async () => {
    const d = await seedDelivery({ state: 'in_transit' })
    await recordPing(d.id, { lat: 1, lng: 1, pingedAt: '2026-10-11T10:05:00Z' })
    await recordPing(d.id, { lat: 2, lng: 2, pingedAt: '2026-10-11T10:00:00Z' }) // older
    expect(await latestPosition(d.id)).toMatchObject({ lat: 1, lng: 1 })         // not overwritten
  })

  it('cannot assign one driver to two deliveries at once', async () => {
    const [a, b] = await seedDeliveries(2)
    const r = await Promise.allSettled([assign(a.id, 7), assign(b.id, 7)])
    expect(r.filter((x) => x.status === 'fulfilled').length).toBe(1)
  })
})

Add a test that the stuck-delivery check surfaces an overdue delivery, and one that out-of-order status events leave the correct final state.

Buy, build or hire?

OptionChoose this whenThe catch
A last-mile or fleet SaaSYour flow fits a vendor's model and you want their driver app and mapsYou fit their statuses, assignment and fees; your own rules and integrations may not map
A courier's own trackingYou outsource delivery entirely to carriersYou see only what each carrier exposes, in their format, with no unified view
Custom buildYou run your own drivers, need your own assignment rules, or a unified tracking view across carriersYou own the state machine, the location handling and the tests that keep statuses honest

How long does it take to build yourself, and what is the risk?

For an experienced team building the delivery state machine, driver assignment, location handling and a customer tracking page, our estimate is 4 to 6 weeks for a first version at a fixed scope, or 6 to 12 weeks with routing, proof of delivery and carrier integrations. The main risk is treating location and status updates as reliable and in-order: they are neither, so a system that lets the latest message win will show pins jumping backwards and statuses that never happened. Order by device time, ignore stale updates, and test the out-of-order path.

Why RAITHub for this

  • State machines and real-time flows in production. RAITHub builds order and fulfilment state machines on TheSkinProof (217 endpoints, 750+ tests) and ran delivery-adjacent engineering in grocery delivery app development.
  • Atomic assignment. The same transactional discipline that prevents overselling and double-booking keeps a driver from being assigned twice.
  • Reliable scheduled work. Idempotent jobs are everyday work, so the stuck-delivery check and ETAs do not depend on anyone watching.

When you don't need us

  • A fleet SaaS fits. If a vendor's statuses and driver app match your flow, use it.
  • You outsource delivery entirely. Then the carriers' own tracking may be enough.
  • You only need the model. The state machine and location handling above are a fair start for your own developer.

How RAITHub would build this

  • Delivery state machine: defined statuses with legal-only transitions, status changes as idempotent events.
  • Location handling: pings ordered by device time, stale ones ignored, history thinned, one server-side ETA.
  • Driver assignment: an atomic, rule-based assigner that cannot double-book a driver.
  • Dispatch and customer views: a stuck-delivery check for dispatch, and an honest customer tracking page.

Timeline: a first version fits the 4 to 6 week fixed-scope range; routing, proof of delivery and carrier integrations push it to 6 to 12 weeks. As a new vertical for RAITHub, this builds on its SaaS development service; a related new-vertical build is a field-service scheduling SaaS.

You receive: the product on accounts you own, tests and CI covering the state machine and location handling, handover runbooks, and full IP under NDA. The next step is the SaaS development service and a free 15-minute audit.

Next step: book the free 15-minute technical audit with your delivery flow and assignment rules, and we will follow up with a written fixed quote.

Frequently asked questions

Why build delivery tracking as a state machine?

Because a delivery has an order to its stages, and skipping or reversing them is a bug. A state machine defines the allowed transitions, so "delivered" can only be reached through the legal path, impossible states never appear, and the customer-facing tracking shows a real status rather than a guess.

How do I handle GPS pings that arrive out of order?

Order them by the device timestamp, not arrival time, and ignore any ping older than the latest one you already have, so the map pin never jumps backwards. Store device time separately from server receive time, because drivers lose signal and sync late.

How do I stop a driver being double-booked?

Assign inside a transaction that checks the driver is available and marks the delivery assigned in one step, so two dispatchers or a repeated auto-assigner cannot both claim the same driver. A test runs two concurrent assignments and asserts exactly one wins.

How do I stop deliveries getting stuck?

Give each state an expected maximum time and run a scheduled job that surfaces deliveries sitting too long to dispatch. A delivery stuck in "picked up" for hours is a lost driver or a lost status update, and a human needs to see it.

Where should the delivery ETA be calculated?

Server-side, from the real state and the latest valid position, so every client (the customer page, dispatch, the driver app) shows the same number. Letting each client compute its own ETA produces conflicting figures and erodes trust in the tracking.

Should I build this or use a fleet SaaS?

Use a fleet SaaS when its statuses, driver app and assignment fit your flow. Build custom when you run your own drivers, need your own assignment rules, or want a unified tracking view across several carriers that no single vendor gives you.

delivery trackinglogisticsstate machinedriver assignmentreal-timesaas

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.