Winning back the customers an auto shop already has
Every shop has a list of people who already trusted it once and never heard from it again. This turns the raw export into three short emails per group, approved once by the owner, sent...
Click through the six steps on the 200-row demo list: the column match, the cleanup, the emails, the simulated campaign and the projection. It is a static copy, so nothing you click is sent.
Open the demo dashboardProblem
An independent repair shop spends money every month to get new people through the door, while its own system holds years of customers who already paid it once. Two groups sit in there untouched. Customers who have not been back in a year or more, and quotes: written up and never sent, or sent and never answered. Those people already asked for a price. Nobody follows up because the export is a mess and writing a few hundred personal emails is a weekend no owner has.
This is the pattern from the CAIC opportunity-audit training: the client asks for more leads at the top, and walking the pipe turns up hundreds of past customers nobody contacted since. The fix is reactivation, not more traffic. The demo business here is invented (Kestrel Auto, a 4-bay shop in Northern Virginia) and so is every customer in it. There is no real owner and no real result yet.

Before
The shop management system (Mitchell 1, Tekmetric, Shopmonkey) can export customers to a spreadsheet. The columns are named whatever that system calls them ("Cust #", "Ph#", "Last RO Desc"). The same customer appears three times under three phone formats. Some rows say "DNC per cust" in a notes column. The owner opens it, scrolls, closes it. The open quotes live in the same system, mostly forgotten after a week.
What I built
The tempting build is "AI writes a personal email to every customer". That is the wrong one: a model writing 300 emails will eventually state a price that was never quoted, mention a job that never happened, or scare someone about their brakes. The constraint: the owner approves every word that can go out, once per group, not once per email. AI writes the template. Code fills every fact from that customer's own row and checks every message before it can be sent.
- Import: the owner uploads whatever their system exports, CSV or Excel. Claude reads the header row and ten sample rows and maps them to the fields it needs (name, email, phone, vehicle, last visit, last job, open quote and its status, consent). The owner sees "14 of 14 columns matched" and only checks the ones Claude flagged as unsure. This is the only AI step before drafting.
- Clean: plain code. Phones and emails normalized, duplicates merged (same email or phone, keep the latest visit), rows with no way to reach them dropped, do-not-contact honored from the notes column and a suppression list that only ever grows.
- Sort: plain code, unit tested. Four groups, quotes first because those people already asked: unsent quotes, unanswered quotes (sent 14 to 120 days ago), away 1 to 2 years, away 2 to 4 years. Still-active customers, anyone gone over 4 years, and phone-only customers are left alone. The owner can change the windows in a plain file.
- Write: Claude drafts one short sequence per group in the owner's voice (three emails over ten days, two for the oldest group), from a style file and an offer file the owner controls. The owner reads it on real customers, edits the wording if they want, and approves the group.
- Send: from the shop's own Gmail. Tuesday to Thursday, 9 to 11am, at most 40 a day, unsubscribe line and business address on every email. Anyone who replies, bounces or opts out gets nothing more. Texting is built but switched off: buying from a shop is not consent to marketing texts.
- Sort the replies: Claude classifies each reply as wants to book, question, not now, stop or wrong person. Stop and wrong person go to suppression immediately. Bookings and questions go to the owner with that customer's history.
- Count: the owner marks a job booked with its amount. Emailed, replied, booked and revenue add up on the dashboard. That funnel is the number a real run would report.
What stays human: every word in the templates, every reply to a customer, every price (a message may only state an amount already on that customer's quote or an offer in the owner's file), and who is on the list.
Demo
The live demo runs on a shared screen in about three minutes, one step at a time. I load a 200-row export built to look like a real one: odd headers, duplicates, four phone formats, blank emails, three do-not-contact notes. Claude maps all 14 columns. The cleaning step turns 200 rows into 177 real customers and 114 who get an email: 12 unsent quotes, 18 unanswered quotes, 48 away 1 to 2 years, 36 away 2 to 4 years.
I flip through finished emails for real rows, like Hassan's unsent $480 brake quote:
I wrote up the quote for your front brake pads and rotors and never sent it, that's on me. It came to $480. If you book by Dec 19, I'll take $25 off. Do you still want it done?
Then the prospect types their own email address and that email lands on their phone while we talk. They reply "Thursday works" from the phone, I press Check, and it comes back sorted as wants to book with that customer's sequence stopped. A simulated campaign then plays 20 scripted replies through the same classifier, badged SIMULATED on screen, and the last step fills the projection with their numbers. Nothing in the demo can reach a customer address.
Result
Labeled projected for revenue, since no shop has run it yet. Measured for what the system does to a list.
recovered = away customers x reply rate x book rate x average ticket + open quotes x close rate x average quote
On the demo list, with deliberately modest rates (8% reply, half of those book, 20% of open quotes close): 84 away customers x 8% x 50% x $191 = $642, plus 30 open quotes x 20% x $540 = $3,240. About $3,900 from one round of emails, almost all of it from the quotes. That is the point a shop owner should hear: the quote pile is worth more than the lapsed list. On a call I ask for their own four numbers and type them in. I don't defend the defaults.
Measured on the 200-row list with sending off, every check passed:
- 14 of 14 columns mapped correctly from the messy headers.
- Cleaning exact: 14 duplicates merged, 9 unreachable dropped, 3 do-not-contact caught.
- 177 of 177 customers placed in the right group, including the edge cases (quote too new, too old).
- 0 rule violations across every rendered email: no message to a suppressed or phone-only customer, no amount that is not on the quote or in the offer file, no scare language, nothing over 700 characters, every send inside the window and under the cap.
- Replies: the stop-or-not decision right on 20 of 20; exact category 19 of 20 (one "wrong person" read as "stop", which suppresses them either way).
- A stronger model judging 28 first emails: 28 of 28 factual, in the owner's voice, and sendable without edits.
What I got wrong first: the original design added one AI-written line per customer about their last job ("the Camry's front brakes were 14 months ago, worth a look before winter"). It made the emails worse. With the line, the judge scored 17 of 30 as in the owner's voice and sendable; without it, 28 of 28. The line had nothing true to add that the template did not already say, so it repeated the offer or invented an upsell. It is off by default now. It earns its place only with richer data, like declined work from the shop system ("you passed on the timing belt in March").
Model cost does not grow with the list: Claude writes eleven templates and maps one header row, so 1,000 customers cost the same to draft as 100. Only reply sorting is per customer.
Stack
Python, FastAPI, pandas for the import, SQLite for the ledger. Claude Sonnet 5.5 for column mapping, the templates and reply sorting (structured outputs, prompt caching on the style and offer files); Claude Fable 5.1 as the eval judge. Gmail API on the business Workspace mailbox. Docker on an Ubuntu home server. Build time: two evenings for the pipeline, eval, dashboard and Gmail previews, plus one pass to rebuild the dashboard as a six-step flow.
Next
A paying version reads the shop system directly instead of an export and matches bookings to the next repair order automatically, so revenue is counted without the owner marking it. It adds declined work as a fourth source (the one place a personal line would be true), turns on texting for customers who consented once the carrier registration clears, and hands a "wants to book" reply straight to the missed-call agent from the speed-to-lead build to offer a time.