Engineering reference: the warranty registration handler architecture
This is the warranty registration handler with the friendly labels removed: the real resource names, the runtime, the serial ledger and the expiry index, the single public Function URL, the reminder schedule, and the IAM scope. If you want to build it rather than understand it, start here.
Key takeaways
- Seven Lambda functions, all Python 3.14 on arm64, wired through one SQS queue with a dead-letter queue.
- One public surface: a single Lambda Function URL on
wrhr-registerthat takes the QR post and the web form — no API Gateway. - Five DynamoDB tables, all on-demand: the warranties record (keyed by serial), the submission ledger, the orders mirror, the opt-out list, and an append-only audit log.
- The warranties table carries a reminder GSI keyed by due date; one EventBridge Scheduler drives the daily reminder sweep and the orders sync.
- One Bedrock model, Claude Haiku 4.5 via Global cross-Region inference, called only by the notifier. 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 only inbound surface is one Lambda Function URL, customer messages go out through SES and SNS, and work is buffered on a single SQS queue.
wrhr-register, an SQS-buffered set of seven Lambdas, five DynamoDB tables with a reminder GSI on wrhr-warranties, Bedrock called only by the notifier, SES and SNS for messages, and two scheduled jobs. One region, one account, no API Gateway.Lambda functions
Seven 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 (wrhr-jobs, with wrhr-jobs-dlq as its dead-letter queue after five attempts) decouples the public form from the slower verification, store, and model calls, and carries a typed job (verify, store, confirm, remind) from one stage to the next.
wrhr-register— the only public surface. Backs the single Lambda Function URL and receives both the QR post and the typed form. Verifies the signed token against the secret, rate-limits per source, de-duplicates againstwrhr-submissionswith a conditional write on the serial (TTL a few minutes), normalises the serial, and enqueues averifyjob. Nothing slow happens here — no order lookup, no compute, no send.wrhr-verify— SQS-triggered onverifyjobs. Looks the serial up inwrhr-orders, confirms the sale exists, checkswrhr-warrantiesfor an existing record, and tests the purchase date against the product’s registration window. A pass enqueues astorejob; a fail records the reason; a genuine near-miss (a receipt with no clean match) goes towrhr-escalator.wrhr-store— SQS-triggered onstorejobs. Reads the warranty rule for the product line, computes the term and exact expiry dates from the verified purchase date, and claims the serial with a conditionalPutItemintowrhr-warranties(attribute_not_exists(serial)), populating the reminder GSI and writingwrhr-audit. On success it enqueues aconfirmjob.wrhr-notify— SQS-triggered onconfirmandremindjobs. Makes the single Bedrock call, validates the draft (right dates, sane length, template fallback), checkswrhr-optout, and sends via SES (email) or SNS (SMS). It writes the send towrhr-audit. This is the only function that calls Bedrock.wrhr-escalator— builds the handover (serial + verified sale + computed coverage + reason), de-duplicates against open cases, and emails the support inbox via SES. This is the lane for later claims and for near-miss confirmations.wrhr-orders-sync— scheduled. Pulls the shop’s sales (platform API, dealer feed, or export) and upserts rows intowrhr-orderskeyed by serial.wrhr-sweep— scheduled. Queries thereminder-dueGSI onwrhr-warrantiesfor reminders due on or before today and not yet sent, enqueues aremindjob for each, and marks the reminder sent and the next one due so it’s only ever fired once.
Data stores, schedules, and messaging
- DynamoDB (all on-demand).
wrhr-warranties— PKserial; one item per registered unit with its coverage, the computed expiry dates, the reminder schedule and “sent” markers, and a state field; a GSIreminder-due(PKdue_date, SKdue_ts) is the sweep’s query path. The conditional write onserialis what guarantees one warranty per unit.wrhr-submissions— PKserial, short-lived with a TTL, the dedup ledger for the register step.wrhr-orders— PKserial, the sales mirror holding purchase date and channel.wrhr-optout— PKcontact(email or E.164), checked before every send.wrhr-audit— PKserial, SKts, append-only, holding each confirmation and reminder and the facts it was built from. - Function URL. One, on
wrhr-register, with signed-token and signature verification in-function;AuthType NONEat the edge because authenticity is enforced by the shared secret and rate limit, not by IAM. No API Gateway. - SES and SNS. SES sends confirmations, reminders, and claim handovers from a verified domain with DKIM; SNS sends SMS reminders to the buyers who opted for them. Both go through
wrhr-notify(customer) orwrhr-escalator(team). - EventBridge Scheduler. Two rules —
wrhr-sweepatrate(1 day)for the reminder sweep, andwrhr-orders-syncatrate(1 hour)to keep the orders mirror current so a sale can be registered soon after it’s made. - Secrets Manager. Two secrets — the form signing key and the order-lookup API key — fetched at call time, never in env vars or the settings doc.
- Bedrock. Model id
anthropic.claude-haiku-4-5via the Global cross-Region inference profile, invoked only bywrhr-notify.
Idempotency and failure handling
Exactly-once registration rests on one thing: the conditional write in wrhr-store. Because PutItem with attribute_not_exists(serial) can succeed only for the first writer, the entire pipeline behind it can be at-least-once without risk. SQS may deliver a job twice, a Lambda may retry after a timeout, two submissions may race — and at most one of them creates the warranty; the rest see the condition fail and treat it as “already registered”. The earlier dedup in wrhr-register is a cheap first filter, not the guarantee. Jobs that keep failing — a malformed payload, an order feed that’s down — land in wrhr-jobs-dlq after five attempts, where they can be inspected and replayed rather than lost or retried forever. The reminder sweep is idempotent for the same reason on the read side: a reminder marked sent is skipped, so a re-run never double-messages.
IAM scope and region
Each function gets its own execution role scoped to exactly what it touches, no wildcards. wrhr-register can read the signing secret, conditionally write wrhr-submissions, and send to wrhr-jobs — it cannot read orders, call Bedrock, or send a message. wrhr-verify can read wrhr-orders and wrhr-warranties and enqueue, nothing more. wrhr-store can conditionally write wrhr-warranties and append to wrhr-audit, but cannot delete from any table and cannot send. wrhr-notify is the only role with bedrock:InvokeModel, scoped to the one Haiku profile; it can publish to SNS and send via SES and read wrhr-optout, but touches no order data. wrhr-escalator can send via SES and read its inputs only. The scheduled functions hold the narrow permissions they need — wrhr-orders-sync writes only wrhr-orders, wrhr-sweep reads the reminder GSI and enqueues — and neither has an inbound surface. 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 is not a data store. An AWS Budgets alarm watches the monthly spend — with SMS the line most likely to move, it’s the cheapest early warning that volume (or a loop) is running hot.
That’s the whole system: a QR scan or a short form, verified against real orders, computed into a dated record that can only ever be written once, explained in plain language, and diarised so the customer hears back at exactly the right moment — with a person on the one decision, a claim, that should never be automated. Seven small functions, five tables, one model call per message, and no always-on anything, for a couple of dollars a month.
Design rules that shaped the build
- One job per function. Seven small Lambdas beat one that does everything; the queue decouples the slow calls from the public form.
- One public surface. Only
wrhr-registeris reachable from outside, on a single Function URL, authenticated by a signed token. - One conditional write is the guarantee.
attribute_not_exists(serial)inwrhr-storemakes registration exactly-once under any retry or race. - Least privilege, per role. Only the notifier can call Bedrock; only verify reads orders; only store writes warranties.
- Dates live with the data. The expiry and reminder dates sit on the record and drive the GSI the sweep queries — no separate timers.
- One region, one model.
eu-west-2throughout; Bedrock Haiku 4.5 via Global inference, called once per message.