Part 7 of 7 · Delivery route planner series ~8 min read

Engineering reference: the delivery route planner architecture

This is the delivery route planner with the friendly labels removed: the real resource names, the runtime, the table key schemas, the morning schedule and the driver-event bus, the IAM scope, and where the routing service fits. If you want to build it rather than understand it, start here.

Key takeaways

  • Eight Lambda functions, all Python 3.14 on arm64, with one SQS queue and a dead-letter queue between the build steps.
  • Four DynamoDB tables, all on-demand: stops, routes, driver progress, and the geocode cache index.
  • EventBridge Scheduler runs the morning build; a separate EventBridge bus carries the driver check-off events.
  • Driver check-off arrives on a Lambda Function URL — no API Gateway; outbound is SES and SNS; the routing key is in Secrets Manager.
  • One Bedrock model, Claude Haiku 4.5 via Global cross-Region inference, called only by the ETA function. Single region, eu-west-2.

The architecture, for engineers

This is the same system as Part 1 with the friendly labels removed and the real resources named. Everything is in one region, eu-west-2 (London), in one account. There is no API Gateway, no NAT Gateway, and nothing always-on: the morning build is fired by EventBridge Scheduler, the one inbound HTTP surface (the driver check-off) is a Lambda Function URL, and the build steps are decoupled by a single SQS queue. The only thing outside AWS is the geocoding and distance-matrix provider.

Engineering architecture: the morning build chain, the check-off bus, eight Lambdas, four DynamoDB tables, Bedrock, S3, and the routing provider A dotted AWS account container labelled eu-west-2 holds three columns. Left column, the triggers: EventBridge Scheduler fires the morning build at six; drpr-drive-sync mirrors the Drive stops sheet to S3 every fifteen minutes; drpr-sweep runs a daily catch-up; lower down, a driver check-off Lambda Function URL feeds an EventBridge bus carrying check-off events. Centre column, the build chain of Lambdas, all Python 3.14 arm64, top to bottom: drpr-gather reads the day’s rows; drpr-geocode turns addresses into coordinates against the cache; drpr-optimize builds the matrix and runs nearest-neighbour then 2-opt; drpr-manifest renders the PDF; drpr-eta computes and sends windows. Below them sit an SQS queue drpr-jobs with a dead-letter queue, and drpr-checkoff which handles the check-off events. Right column, four DynamoDB tables on-demand — drpr-stops, drpr-routes, drpr-progress, and drpr-geocache — plus an S3 bucket drpr-files for PDFs and the geocode cache, a Bedrock box for Haiku 4.5, and Secrets Manager for the routing API key. Arrows: Scheduler to gather; the gather-geocode-optimize-manifest-eta chain downward; gather writes drpr-stops; geocode reads and writes drpr-geocache and calls the external geocoding and matrix provider shown below the container; optimize writes drpr-routes; manifest writes the PDF to drpr-files; eta reads drpr-progress, calls Bedrock, and sends to customers via SES and SNS shown below; the driver check-off URL feeds the EventBridge bus, which triggers drpr-eta to recompute. The geocoding provider, the customers, and the driver are external boxes below the container. AWS account · eu-west-2 EventBridge Scheduler morning build — 06:00 drpr-drive-sync Drive → S3, every 15m drpr-sweep daily catch-up Driver check-off Lambda Function URL EventBridge bus check-off events drpr-gather read the day’s rows drpr-geocode address → lat/long drpr-optimize matrix, NN, 2-opt drpr-manifest render PDF drpr-eta windows + send drpr-jobs SQS + drpr-jobs-dlq drpr-checkoff handle check-offs recompute drpr-stops PK stop_date#stop_id drpr-routes PK route_date, SK van drpr-progress PK route_id, SK seq drpr-geocache PK addr_hash S3: drpr-files PDFs + geocode cache Bedrock Haiku 4.5, Global Secrets Manager routing API key Geocode / matrix external provider Customers SES email / SNS SMS Driver manifest in, taps done All Lambdas: Python 3.14, arm64 · CloudWatch Logs 7-day retention · AWS Budgets cost alarm
Fig 7. The delivery route planner drawn for engineers: a scheduled build chain of five Lambdas, an SQS buffer, a Function-URL check-off feeding an EventBridge bus, four DynamoDB tables, S3, Bedrock called only by the ETA function, and one external routing provider. One region, one account, no API Gateway.

Lambda functions

Eight functions, all Python 3.14 on arm64, all with CloudWatch Logs at 7-day retention. Each does one job and hands off; the SQS queue (drpr-jobs, with drpr-jobs-dlq as its dead-letter queue after five attempts) decouples the build steps so a slow geocode or matrix call can’t wedge the whole morning.

  • drpr-gather — fired by EventBridge Scheduler. Reads the day’s rows from the mirror, validates window, size, and address, writes clean rows to drpr-stops and flagged rows to a flagged list, and enqueues the build.
  • drpr-geocode — resolves each address to a coordinate: reads drpr-geocache first, and only on a miss calls the external provider with the key from Secrets Manager, writing the result back to the cache.
  • drpr-optimize — builds the distance/time matrix (hosted service or haversine fallback), runs the nearest-neighbour seed and 2-opt with time-window and capacity constraints, and writes the ordered route to drpr-routes.
  • drpr-manifest — joins the route to the stop details, renders the manifest PDF, writes it to drpr-files, and sends the driver a link via SES or SNS.
  • drpr-eta — computes a window per stop, makes the single Bedrock call for the wording, sends via SES/SNS, and on each check-off event recomputes the remaining windows from drpr-progress.
  • drpr-checkoff — backs the driver check-off Function URL; validates the event, writes it to drpr-progress, and puts a check-off event on the EventBridge bus that wakes drpr-eta.
  • drpr-sweep — scheduled daily. Catches rounds that never completed, stops with no check-off by end of day, and stale cache entries, and surfaces them to whoever runs the round.
  • drpr-drive-sync — scheduled every 15 minutes. Mirrors the Drive stops sheet and settings doc into S3 and upserts into drpr-stops so the morning build never depends on a live Drive call.

Data stores, schedules, and messaging

  • DynamoDB (all on-demand). drpr-stops — PK stop_date#stop_id, one clean stop per item with coordinate, window, size, and access note. drpr-routes — PK route_date, SK van, the ordered stop sequence and the matrix metadata for the day. drpr-progress — PK route_id, SK seq, the driver check-offs with actual completion times. drpr-geocache — PK addr_hash (the normalised-address key), the stored coordinate and confidence.
  • S3. drpr-files — the rendered manifest PDFs (under a per-date prefix, lifecycle-expired after 30 days) and the geocode cache blobs that drpr-geocache indexes.
  • EventBridge. Scheduler runs the morning build (a daily cron before the working day), the 15-minute drpr-drive-sync, and the daily drpr-sweep. A separate event bus carries the driver check-off events from drpr-checkoff to drpr-eta.
  • SES and SNS. SES (verified domain, DKIM) sends the manifest link and the ETA emails; SNS sends the ETA SMS where a stop or the settings doc calls for it. Inbound customer replies route to a human, not back into the planner.
  • Secrets Manager. One secret — the geocoding/matrix API key — fetched at call time, never in env vars or the sheet.
  • Bedrock. Model id anthropic.claude-haiku-4-5 via the Global cross-Region inference profile, invoked only by drpr-eta.

IAM scope and region

Each function gets its own execution role scoped to exactly what it touches, no wildcards. drpr-gather can read the mirror and write drpr-stops; it can’t call Bedrock or the routing provider. drpr-geocode is the only role that can read the routing secret, and it can read and write drpr-geocache and nothing else. drpr-optimize reads drpr-stops and writes drpr-routes — no outbound network, no send permissions. drpr-eta is the only role with bedrock:InvokeModel, scoped to the one Haiku profile, plus SES and SNS send and read on drpr-progress; it cannot write the route. drpr-checkoff can write drpr-progress and put events on the bus, nothing more. Everything runs in eu-west-2; the only cross-Region path is Bedrock’s Global inference profile, which routes the model call for capacity and holds no data. An AWS Budgets alarm watches the monthly spend and notifies if it drifts above a few dollars — the cheapest possible smoke detector for a runaway geocode loop or a stuck matrix call.

Design rules that shaped the build

  • One job per function. Eight small Lambdas beat one that does everything; the queue decouples the slow calls.
  • One public surface. Only the driver check-off Function URL is reachable from outside — no API Gateway.
  • Least privilege, per role. Only drpr-eta calls Bedrock; only drpr-geocode reads the routing secret.
  • State in DynamoDB, blobs in S3. Tables for stops, routes, progress, and the geocode index; S3 for PDFs and cache.
  • One region, one model. eu-west-2 throughout; Bedrock Haiku 4.5 via Global inference, called once per message.
  • A budget alarm is a smoke detector. The cheapest way to learn a routing call looped is a Budgets alert.
All posts