On February 4th I started looking for a used Toyota Tacoma. On August 5th I bought one. In between, I built a piece of software whose entire job was to watch the used truck market for me, score everything it found, and email me a briefing every morning at seven.
Yesterday it became useless. Today I turned it off. This is what it did, what it got wrong, and the one thing I’d tell anyone building something similar.
1,058Trucks tracked
487Emails parsed
183Days running
19Dealers contacted
5Test drives
1Truck bought
What it actually was
A dashboard, a database, and a robot that read my mail.
I set up saved searches on seven car sites — cars.com, AutoTempest, CarGurus, CarMax, Carvana, TrueCar, Autotrader — and pointed every one of them at a dedicated address, tacoma@jbmangum.com. That address routed into a Cloudflare Worker. Every alert email that landed got handed to Claude Haiku with instructions to pull out structured listings: year, trim, price, mileage, VIN, color, drivetrain, dealer, photo.
Anything that survived the filters got scored out of 100 and written to a database. Every morning at 7am a cron job swept fresh inventory, re-scored the board, and sent me a digest of what had changed overnight — new trucks, price drops, and anything that had quietly disappeared.
The part I got wrong for five months
The first version was email-only. Saved searches came in, Claude parsed them, listings came out. It worked, in the sense that it produced rows. It took me until late July to measure whether those rows were any good.
They weren’t. Here’s the coverage across everything that had come in through the email pipeline:
A marketing email is written to sell you a truck, not to describe one.
That sentence is the whole problem. Dealer alert emails are conversion instruments. They lead with a price and a photo and a button. The facts I needed to make a decision — drivetrain, cab configuration, bed length — are exactly the facts that don’t help sell, so they’re frequently just absent. No parser fixes that. I had spent months tuning the extraction when the extraction was already close to the ceiling of what the input contained.
Worse, one of my seven sources was quietly poisoning the well. My CarGurus saved search had never been scoped to Tacomas at all — it was a generic “price search from $19,599”. It sent 177 emails and produced 21 usable listings. It was my highest-volume source and my worst one.
The fix
In late July I inverted the architecture. The primary intake became a daily sweep of a vehicle listings API, which returns a VIN on every single row. A VIN can be decoded for free against the NHTSA’s public database, and that decode knows the drivetrain, the cab and the bed with certainty. Email alerts got demoted to a secondary net for whatever the API missed.
Coverage went to 100% on VIN, drivetrain, cab and bed. Photo coverage went from 0% to 100%.
The rule I extracted, and the reason I’m writing this down: when your data is bad, improving the parser is the expensive way to fail. Go find a better source. I burned five months of intermittent evenings on the wrong end of that pipeline.
Scoring, and why the top of the board lied to me
Every truck got a grade from A to F, out of 100. The score weighted price against the market, mileage, model year, drivetrain, trim and configuration. The dashboard sorted on it, and the morning digest ranked on it, which meant the score was effectively making my shortlist for me.
Then a dealer put a 2022 TRD Off-Road on the market at $16,094 and my tracker cheerfully ranked it Grade A, 92 out of 100, top of the watchlist. It was bait. The price was fictional, the sort of number that exists to get you to call. My scoring model had no concept of “this is too good to be true,” so it did what I’d told it to and rewarded the best price it had ever seen.
I added an accident penalty and some sanity bounds after that. But the deeper point stands: a scoring function is a statement about what you value, and anything that games the inputs games you. Mine assumed good faith from used car dealers, which in hindsight is a bold assumption.
Where the trucks came from
Note the shape of that: the API sweep ran for twelve days and produced more listings than any email source managed in three months.
The funnel
1,058 trucks went in. Here’s how far they got.
A 0.5% conversion from “seen” to “sat in.” Which sounds brutal until you remember the alternative was me refreshing cars.com by hand at midnight, seeing perhaps thirty trucks a month, and having no memory of which ones had dropped in price.
The five I drove: two 2021 SR5s in South Carolina, two brand-new 2026 Double Cabs in North Carolina, and a 2020 SR5 in Hendersonville. Average asking price across everything tracked was $35,857 at an average 35,944 miles.
What I’d keep
Three things earned their keep, and I’d build all of them again:
Price history on every listing. 1,044 of the 1,058 rows carried a full price trail. Watching a truck sit at $37,000 for six weeks and then drop twice in nine days tells you something no single snapshot can: that dealer wants out. That’s negotiating information and it’s invisible unless something is watching daily.
A record of what I’d already rejected. Over six months the same trucks resurface under different listings, on different sites, at slightly different prices. Being able to say “this is the Salisbury truck, you passed on it in June, it’s now $1,400 cheaper” is genuinely useful and completely impossible to do from memory.
Hard filters, applied without mercy. No white trucks, no 2WD. 150 two-wheel-drive trucks got dropped automatically. I never saw them, never thought about them, never spent a Saturday driving to look at one.
What I’d skip
The email pipeline, honestly. It was the most interesting thing to build — MIME parsing, LLM extraction, sender allowlists to stop the thing being turned into an open relay — and it was the least valuable thing in the system. Twelve days of API sweeps beat three months of parsed marketing mail.
I’d also skip the last round of scoring refinements. By late July I was tuning weights on a model that was about to become irrelevant, because I was two weeks from buying a truck. Knowing when a project is nearly over is a skill I do not have.
Turning it off
The shutdown was the easy part, and worth doing properly. The daily cron is gone. The database — 1,058 listings, 487 raw emails — is exported and restored locally as a verified SQLite file. All 434 vehicle photos are pulled out of object storage and sitting in a folder. These screenshots exist because I took them before deleting the site rather than after, which I recommend.
Everything else — the worker, the database, the bucket, the dashboard — is deleted. The repository is archived. Total infrastructure cost of the whole exercise was a couple of dollars a month in API calls.
Build the thing that finds the truck. Then, when you have the truck, have the discipline to switch it off.
There’s a version of this where I keep it running because I’m fond of it. It’s a good dashboard. It looks great. It does something clever. And it would sit there costing money and sending me a briefing every morning about a decision I already made.
The last digest went out at 7am yesterday. Nobody needed to read it.
I open-sourced the whole thing
Everything described above — the scoring, the VIN decoding, the email parser, the API sweep, the dashboard — is on GitHub under MIT. I stripped my data out and lifted the search criteria into one config file, so pointing it at a 4Runner, an F-150 or a Wrangler is a config change, not a code change.
If you’re about to start a hunt of your own, take it. And take the lesson that cost me five months with it: start with the inventory API, not the email alerts.
github.com/realjbmangum/vehicle-hunt →
MIT licence · 83 tests · Cloudflare Workers + D1 + R2 · Astro · Claude Haiku
Runs inside the free tiers. Real cost was a couple of dollars a month in API calls.
Tacoma Field Tracker · Feb 4 – Aug 6, 2026
Astro · Cloudflare Workers, Pages, D1, R2 · Claude Haiku · NHTSA vPIC · Auto.dev
1,058 listings · 487 emails · 434 photos · 1 truck







