Part 1 of 7 · Delivery route planner series ~10 min read

A delivery route planner on AWS for a few dollars a month

Every morning a delivery or service round starts with the same unglamorous puzzle: here are today’s stops, here’s the van, what order do we do them in? Most small teams solve it with a driver’s instinct and a paper list, which is fine until there are thirty stops, three of them with fixed time windows, and one customer ringing to ask where the van is. This post walks through the design of a small planner that turns the day’s stops into an optimised route, hands the driver a clean manifest, and tells each customer when to expect the van — without ever taking the wheel.

Key takeaways

  • Each morning the planner gathers the day’s stops, geocodes the addresses, and works out a sensible order.
  • The optimiser is plain code — a distance matrix, a nearest-neighbour seed, and 2-opt — with no model on the route.
  • Time windows and the van’s capacity are hard constraints: a stop that won’t fit either is never forced in.
  • The driver gets an ordered manifest with access notes; each customer gets an ETA window that updates as stops are done.
  • Designed on AWS for about $3.10/month at roughly 40 stops a day. The route is a proposal — the driver stays in charge.

The whole system on one page

Before any code, here’s the shape of what we’re designing. A delivery or service business starts every day with the same puzzle — a list of stops, one or two vans, and a finite number of daylight hours — and solves it the same way: someone eyeballs the addresses and picks an order that feels about right. That works at five stops and falls apart at thirty, especially once a few of them have to happen inside a window. The system below does that ordering properly, the moment the day’s list is settled, and tells everyone — driver and customers — what to expect.

System architecture: the day’s stops, the depot, and drivers and customers around four pieces inside AWS At the top, three external boxes in a row. Far left, “The day’s stops” — today’s deliveries or service calls from the orders sheet, one row per stop with the address, time window, size or duration, and access notes, mirrored into AWS. Centre, “Depot and vehicles” — a short settings doc with the depot address, the vans and their capacities, working hours, and the average time spent at a stop. Far right, “Drivers and customers” — the driver who receives the ordered manifest and the customers who receive an ETA window for their stop. Each connects by an arrow to the AWS account container below. The day’s stops feed in; the depot and vehicles ground every calculation; the driver and customers receive the manifest and the ETAs. Inside the AWS account are four components in a row. First, Gather — each morning a scheduled job reads the day’s stops, geocodes each address against a cache, and validates the time windows and sizes. Second, Optimise — plain code builds a distance and time matrix, seeds a route with nearest-neighbour, and improves it with 2-opt, honouring time windows and capacity. Third, Manifest — renders the ordered route into a driver manifest PDF with addresses and access notes and drops it in S3. Fourth, ETAs — sends each customer an arrival window and updates it as the driver checks off stops. Arrows flow left to right through the four pieces. A note at the bottom reads: the route is a proposal the driver can override, plain code does the optimising, and a model only drafts the customer messages. The day’s stops orders sheet, one row per stop Depot and vehicles depot, vans, hours, settings Drivers and customers manifest in, ETAs out stops in grounds manifest & ETAs out AWS account Gather geocode the stops, check windows Optimise matrix, nearest- neighbour, 2-opt Manifest ordered stops, PDF to the driver ETAs a window per stop, updated live stops route order The route is a proposal the driver can override — plain code optimises, and a model only drafts the customer messages.
Fig 1. Three things outside, four pieces inside AWS. The day’s stops flow in; the depot and vehicle settings ground every calculation. Gather geocodes and checks the stops, Optimise orders them, Manifest hands the driver a list, and ETAs keep each customer informed.

What you set up once (the outside)

  • The day’s stops. A Google Sheet in a Drive folder (or your existing orders system) with one row per stop: a stop reference, the customer name, the delivery address, a phone or email for the ETA, an optional time window (“before noon”, “2–4pm”), the size or weight — or for a service round, the minutes the job takes — and a free-text access note (“side gate, dog”). You already produce most of this in the normal course of taking orders; this just keeps it where the planner can read it each morning. Part 2 covers exactly how it’s gathered.
  • Depot and vehicles. One short settings doc in the same folder. It holds the depot address every route starts and ends at, the vans and what each can carry (boxes, weight, or pallet count), the working day (“08:00 to 17:00”), the average time a driver spends at a stop, and which geocoding and distance-matrix provider to use. Changing the cut-off time or adding a second van is an edit here — not a deploy.
  • Drivers and customers. The driver opens a link on their phone and sees the manifest — the stops in order, each with its address, window, and access note. The customer gets a single message: “Your delivery is due between 10:30 and 11:30 today,” updated if the round runs ahead or behind. Neither side ever sees the optimiser’s internals; they see a list and a window.

What runs every morning (the inside)

  • Gather. A scheduled job wakes at a set time, reads the day’s stops from the mirror, and turns each address into a latitude and longitude. Addresses repeat — the same streets come up week after week — so a cache means most lookups cost nothing. It also validates each row: a stop with no usable address, an impossible window, or a load bigger than any van is pulled out and flagged rather than quietly breaking the route. This is Part 2.
  • Optimise. With clean coordinates in hand, plain Python builds a matrix of travel times between the depot and every stop, seeds a route by nearest-neighbour (start at the depot, always go to the closest stop not yet visited), then runs 2-opt to uncross any legs that double back. Time windows and van capacity are checked on every candidate route; anything that would make the van late or overloaded is rejected outright. No model touches this. This is Part 3.
  • Manifest. The ordered route is rendered into a driver manifest — stop 1, stop 2, stop 3, each with the address, the window, and the access note — written once to a PDF and dropped in S3, then sent to the driver as a link. The driver can re-order or skip; the manifest is the suggested order, not a locked instruction. This is Part 4.
  • ETAs. As the round is planned, each customer gets an arrival window for their stop, the wording drafted by one small Bedrock call. As the driver checks stops off, those events flow back in and the remaining windows are recomputed from where the van actually is, so a slow morning slides everyone’s ETA without anyone retyping a thing. This is Part 5.

In plain words

At 6am a florist’s sheet has 34 deliveries for the day, six of them flagged “before noon” for offices that lock up at lunch. The planner geocodes the addresses — 31 are already in the cache from previous weeks, so only three are looked up — and orders the round so the six timed stops fall in the morning, the van loops out and back without crossing its own path, and the heaviest crates come off first. The driver opens the manifest at 7:40: 34 stops, in order, each with the postcode, the window, and notes like “reception, ask for Priya.” Every customer has already had a text: “Your flowers are due between 9:15 and 10:15 today.” When the driver hits traffic and falls twenty minutes behind by stop 12, the afternoon windows quietly shift and the next few customers get an updated text — no one rings to ask.

The cost of running this is about $3.10 a month at that volume. The cost of not running it is the doubled-back miles nobody measures, the “before noon” stop reached at 12:20, and the steady trickle of “where’s my delivery?” calls that land on whoever is nearest the phone.

Design rules that shaped every decision

  • The plan is a proposal. The manifest is the suggested order; the driver can re-order, skip, or insert a stop at any time.
  • Plain code optimises. The matrix, nearest-neighbour, and 2-opt are deterministic Python — no model decides the route.
  • Windows and capacity have a veto. A route that arrives late or overloads a van is rejected, never “optimised” into existence.
  • The model only writes words. Bedrock drafts the customer ETA message and nothing else; swap it for a template and lose nothing structural.
  • Settings live in a doc. Depot, vans, hours, and the average stop time change without a deploy.
  • Pay per morning, not per hour. Nothing runs when there are no stops to plan, so the idle bill is zero.

Why this shape

Most small rounds are planned one of three ways: the driver orders the stops from memory, someone senior spends twenty minutes with a map before the vans roll, or there’s an expensive fleet product that assumes a depot of fifty vehicles and bills accordingly. Memory is fine until the new driver doesn’t know the shortcuts. The twenty-minute map is the first thing dropped on a busy morning, and it can’t react when stop 12 runs late. And the fleet product solves a problem a two-van florist doesn’t have, at a price that doesn’t fit.

The shape above keeps the orders sheet you already maintain as the list of stops, leans on a hosted matrix service (or a simple distance estimate) for the travel times, and adds a small system that does the ordering properly every morning for the cost of a coffee. The optimisation that people assume needs AI is the boring, well-understood part — nearest-neighbour and 2-opt have been ordering routes for decades — so there’s no model on the path that matters, and the one place a model does appear, the customer text, can be turned off without breaking anything.

The next four posts walk through each piece in turn: how the day’s stops get gathered, how a route gets optimised, how a driver manifest gets built, and how customer ETAs get sent. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts