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.
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 todrpr-stopsand flagged rows to a flagged list, and enqueues the build.drpr-geocode— resolves each address to a coordinate: readsdrpr-geocachefirst, 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 todrpr-routes.drpr-manifest— joins the route to the stop details, renders the manifest PDF, writes it todrpr-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 fromdrpr-progress.drpr-checkoff— backs the driver check-off Function URL; validates the event, writes it todrpr-progress, and puts a check-off event on the EventBridge bus that wakesdrpr-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 intodrpr-stopsso the morning build never depends on a live Drive call.
Data stores, schedules, and messaging
- DynamoDB (all on-demand).
drpr-stops— PKstop_date#stop_id, one clean stop per item with coordinate, window, size, and access note.drpr-routes— PKroute_date, SKvan, the ordered stop sequence and the matrix metadata for the day.drpr-progress— PKroute_id, SKseq, the driver check-offs with actual completion times.drpr-geocache— PKaddr_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 thatdrpr-geocacheindexes. - EventBridge. Scheduler runs the morning build (a daily
cronbefore the working day), the 15-minutedrpr-drive-sync, and the dailydrpr-sweep. A separate event bus carries the driver check-off events fromdrpr-checkofftodrpr-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-5via the Global cross-Region inference profile, invoked only bydrpr-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-etacalls Bedrock; onlydrpr-geocodereads 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-2throughout; 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.