AWS Builder Center
Making product recalls executable with Aurora DSQL and Vercel

Making product recalls executable with Aurora DSQL and Vercel

A recall is just information until something acts on it. I built SafeState to block recalled products at the moment they are resold, and the hardest part turned out to be a database problem. Here is how one shared guard row and Aurora DSQL's multi-region strong consistency make sure a recalled item can never read as safe, in any region.

A product recall today is basically a notice. It lives on a webpage, or a PDF, or an email that somebody is supposed to read. Say the problem out loud and it gets uncomfortable fast. A recalled crib can be listed and sold to another family, and nobody in that sale ever sees the recall. Reselling recalled goods is actually illegal, and recalled infant products have killed kids.
I spent this hackathon building something to close that gap. I called it SafeState, and the idea is small: make the recall do something. When a second-hand item is listed or sold, the marketplace checks SafeState first, and recalled units get blocked right at checkout. It is precise down to the serial number, so safe units still sell.
It runs on a Next.js front end on Vercel, with Amazon Aurora DSQL behind it.

Why DSQL is the whole point here

The promise SafeState has to keep is this: the moment a recall lands in any region, no marketplace anywhere should ever read that product as "safe" again.
That is a strong consistency problem, not a nice-to-have. If there is any window where a recalled product still looks safe, that is exactly when it gets sold. An eventually consistent store or a nightly sync leaves that window open. DSQL's active-active, multi-region setup with strong consistency is what closes it.
I set up a real peered cluster across us-east-1 and us-east-2, with us-west-2 as the witness. Write a recall through one region's endpoint and you can read it back from the other region right away.

The one trick that makes it work

DSQL runs on snapshot isolation (PostgreSQL REPEATABLE READ) with optimistic concurrency. It catches write-write conflicts at commit time. Snapshot isolation will not protect you from write skew, so I had to design around that.
To guarantee that a recall and a sale of the same product actually collide, I make both of them write the same row. Every model has one safety_guard row that holds its status and an epoch number.
1
2
3
4
5
6
7
8
9
// authorize-transfer, simplified. The AUTHORIZED path touches the SAME guard
// row a concurrent recall writes, so DSQL is forced to detect the conflict.
await client.query("BEGIN");
await client.query("SELECT epoch FROM safety_guard WHERE model_id = $1", [modelId]);
// ...evaluate every active directive against THIS unit's serial...
await client.query("INSERT INTO ownership_transfers (...) VALUES (...)");
await client.query("UPDATE product_instances SET current_owner_id = $1 WHERE id = $2", [buyer, id]);
await client.query("UPDATE safety_guard SET updated_at = now() WHERE model_id = $1", [modelId]); // the conflict-forcing write
await client.query("COMMIT"); // the loser throws SQLSTATE 40001 / OC000 here
If the recall commits first, the sale's COMMIT throws SQLSTATE 40001 (OC000). A small wrapper catches it, backs off with some jitter, and runs the whole transaction again. The second time around it reads the recalled state and returns BLOCKED. So there is no version of events where a recalled product slips through as safe.

Proving it under load

A guarantee you cannot see is just a claim, so I put a stress test in the app: a hundred concurrent attempts to buy a recalled unit, fired at the live cluster at once. Every one comes back blocked, and zero recalled units sell.
It also taught me to trust the model. My first version put a SELECT ... FOR UPDATE on the guard row in every check, which made two unrelated blocked checks conflict with each other for no reason. The conflict I actually want is between a recall and an authorized sale, and both already write the guard row. Dropping the FOR UPDATE from the read made the load test clean and left the real conflict untouched.

Two databases, on purpose

Not everything needs a distributed transaction. Every public check, verify, and scan is an event worth counting, and every Safety Receipt is a small durable record. That stream is write-heavy and key-accessed, so it lives in Amazon DynamoDB, while Aurora DSQL keeps the transactional core. Right tool per workload, which kept the hot path clean.

The Vercel side

Route handlers talk to DSQL over the normal Postgres protocol, but auth is a short-lived IAM token minted per connection with @aws-sdk/dsql-signer. There is no database password sitting in an env var anywhere. A Vercel Cron job pulls real recalls from the public CPSC API once a day, and Claude reads messy second-hand listings and figures out which recall they match, sending uncertain ones to a review queue. When a recall is issued, the app also emails the current owners of affected units, and the public check fans out to CPSC, FDA, and NHTSA at once.
One thing that cost me an hour: Vercel functions run on Lambda, and Lambda reserves AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION. You cannot set those as env vars, so I pass the DSQL credentials under different names and hand them to the signer directly.

A few things that helped

If you build on DSQL, pick a problem where being correct under concurrency is the actual product, not a side detail. Make your conflicting operations write the same row so OCC has something to catch. And write the retry-on-40001 wrapper before anything else.
Recalls should stop being PDFs and start being decisions. Aurora DSQL and Vercel got me there over a weekend.
Live: https://safestate.vercel.app , code: https://github.com/usv240/safestate
Sharing this as part of my submission to the H0: Hack the Zero Stack hackathon. #H0Hackathon
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article