Case study: independent product

BuffrHQ

An all-in-one CRM for mobile auto detailers: scheduling, client lookup, invoicing, inventory and route planning. Designed, built and shipped alone.

Download on the App Store

Role
Solo: product, design, four surfaces, backend
Timeline
Mar 2026 → now · still shipping updates
Stack
React Native / Expo, Vue 3, Node, Postgres, Mapbox
Status
Live on the App Store, under Buffr LLC
Source
Private (reason below)

The problem

Every tool built for detailers assumes a desk and a receptionist.

Mobile detailers work out of a vehicle, not a shop. Between jobs they are booking work, looking up returning clients, taking payment and tracking how much product they burned through. The software sold to them was designed for somebody sitting down.

The constraint that shaped everything else: the person using this is standing outside in sun glare with one dry hand and about fifteen seconds before the next thing needs their attention. That is not a styling problem. It decides how many taps a job transition can take, how large a target has to be, what belongs on a home screen at all, and which features are allowed to require typing.

Constraints

What was fixed before I wrote anything.

  • No team, no specification, no design partner. Every decision was mine, and every one of them came back to me later, usually at the worst time.
  • Real money and real location data from the first release. That is why the source is private, and it is why several of the calls below are risk decisions rather than engineering ones.
  • One person's operating budget. Anything priced per request had to justify itself against a subscription a solo detailer will actually pay.
  • The whole of it. Auth, edge cases, App Store review, subscription entitlements, and the unglamorous last twenty percent a team normally absorbs.

What I decided,
and what I rejected

Four calls, and what each one cost.

These are the decisions I would want to be asked about in an interview, so they are the ones written down. Each has a cost, because all of them did.

Call 01Route ordering without a routing bill

Commercial route-optimization APIs charge per request, and that cost scales badly against a price a solo operator will pay.

So the owner's dashboard has an Optimize Route button: a nearest-neighbor heuristic over haversine distance, computed locally and anchored to the earliest scheduled job. The planner at the top of my home page is the same algorithm. Jobs missing coordinates go to the end of the list instead of quietly disappearing from it. On the phone, where detailers actually are, the day simply follows the schedule.

The drawn route calls a free public routing service for a road-following polyline, with a straight-line fallback if it is slow or unreachable, so the map degrades to useful rather than to blank. Turn-by-turn navigation is handed off to Apple Maps or Google Maps. I have no business trying to out-navigate them, and no detailer wants me to.

In hindsight it is the one feature I would cut entirely (more on that below).

Chose
Nearest-neighbor ordering on the owner's dashboard, behind an Optimize Route button; navigation handed to the maps app they already use.
Rejected
A paid optimization API, and building turn-by-turn navigation in-app.
The cost
The tour is not optimal, and at three or four jobs a day there is little to optimize. On the phone, stops simply follow the schedule.

Call 02Buy the recognition; engineer the uncertainty

Detailers rarely remember names. They always recognize the car. Matching the lookup key to how the user actually thinks was the highest-leverage product decision in the app: point the camera at a plate and the returning client comes up.

Recognition itself is a solved problem sold as an API, and it is not where my advantage lives. The app captures a frame and hands it to my backend, which calls a commercial ALPR service. The token never ships to the client. In the bundle, anyone can pull it out of the IPA and spend my quota.

The engineering that mattered was everything around the call. A confidence threshold, below which the app shows what it read and asks the user to confirm instead of silently searching for the wrong plate. A manual entry path that is always visible, never a fallback you have to discover. Recognition fails in rain, at angles and on novelty plates, and a feature that only works in good conditions is one people stop trusting. And a region selector, because plate strings are only unique within an issuing state and two clients can genuinely share one.

The interesting part of integrating an ML service is rarely the call. It is deciding what to do when it is unsure.

Chose
Buy recognition, proxy it server-side, and design the interface around the failure case.
Rejected
On-device OCR, and shipping the vendor token inside the app.
The cost
A network round trip, a per-scan cost, and a hard dependency on somebody else's uptime.

Call 03No card processing, on purpose

The obvious move is to integrate a card processor and take a cut of every job. I chose not to. Onboarding a solo operator into a payments platform means underwriting, KYC, payout delays, chargeback exposure and a compliance surface I would be carrying alone.

Detailers already get paid through Cash App, Venmo, PayPal and Zelle. So the invoice generates a QR pay page with whichever of those the business has configured, the customer pays through a channel they already trust, and the detailer marks it settled. Tips are chosen by the customer at pay time rather than pre-set by the detailer, which converts better and is less awkward for both of them.

It is a smaller-looking feature that removed an entire category of risk from a one-person company.

Chose
Four payment handles behind a generated QR pay page.
Rejected
Card processing, and revenue from a take rate.
The cost
No payment revenue, and settlement stays a manual step. Reconciliation is the detailer's job.

Call 04Permissions before there were users to permission

Access is gated on two independent axes: what your role allows, and what your subscription tier includes. A feature has to clear both.

That is over-engineering for launch, and it was, right up until the first team account, at which point retrofitting it would have meant auditing every screen in the app.

What made it survivable in production was a deliberate fail-safe: if the permission set cannot be loaded, a small set of account-essential screens stay reachable. An empty permission set rendering a dead app is a much worse failure than briefly showing someone a button they cannot use.

Chose
Two-axis permissions in the first release, with a fail-safe floor.
Rejected
Ship single-user, add roles when a team asks for them.
The cost
Weeks of work before a single team existed, and a check on every screen to keep correct.

What it does

Four client surfaces over one backend.

A React Native app for the detailer in the field. A Vue 3 dashboard for the owner planning a day or reconciling a week. A public booking site per business, so their customers can request work that lands in the app as an incoming job. And a platform admin for me. Behind all of it, a Node and Postgres backend handling auth, integrations, scheduled work, and push, SMS and email notification depending on urgency and on what the recipient has.

Services are priced the way detailers actually sell them: one price for a small vehicle plus an increment per size step, rather than four unrelated prices per service. Setting up a price list stops being data entry and becomes one number and three adjustments. The distinction between no increment configured and an increment of zero turned out to matter, and getting it wrong is exactly the kind of bug that quietly overcharges someone's customer.

BuffrHQ home screen showing the next job en route, low-stock and overdue-invoice alerts, five quick actions, and today's three jobs with times, vehicles and prices.
TodayThe daily driver: what is happening now, what needs attention, and the five things worth a single tap.
Plate scanner screen: a captured photo of a license plate with the characters pixelated, a state selector, a manual plate entry field, and a result panel offering to create a new client.
Plate scannerPoint the camera at a plate to pull up a returning client. Manual entry and the state selector sit directly underneath, always. The plate is redacted here because it is a real one.
Job detail screen with a four-step progress track (Scheduled, En Route, In Progress, Complete), a large Start Job button, services and total, service address, and customer notes.
Job detailScheduled → En route → In progress → Complete. Clients are notified on transition, which ended the most common phone call a detailer gets: “are you still coming?”
Invoice screen listing two line items, a subtotal, tax and total, with actions to resend the invoice, collect via QR, or mark it paid.
InvoiceGenerated on site, from the services on the job. Marking it paid is one tap and one thumb.
QR Pay screen showing a large QR code to show the client, a shareable pay link, and the four configured payment methods: Cash App, Venmo, PayPal and Zelle.
QR payCash App, Venmo, PayPal, Zelle. The customer pays through something already on their phone.
A detailing business's public booking page in a mobile browser, showing the business name, rating, location, an about section, a service list with prices and durations, and a Book Now button.
Public booking pageEvery business gets one. Requests arrive in the app as an incoming job rather than as an email to answer later.

Scroll or arrow through · six of the app's screens

Outcome

Live, and still mine to maintain.

  • On the App Store with in-app subscriptions, operating under Buffr LLC, and still shipping updates.
  • First commit to release in under three months, solo, across four client surfaces and one backend.
  • Automatic status notification removed the single most common support conversation a detailer has.
  • Plate lookup, barcode inventory, route planning, a four-app QR pay page and role-scoped multi-tenancy all shipped in the first year.

I am not going to publish install or revenue numbers. It is a young product in a small market and the honest version of that chart is not impressive yet. What it does prove is delivery: I chose the scope, made every call above, and got it through App Store review without anyone to hand the hard parts to.

Why the source is private

BuffrHQ handles payment handles, addresses and location data for real businesses. Git history exposes every commit, not the current state. One config file committed and removed a year later is still trivially recoverable. The case study carries the value here; the code does not need to. I am happy to walk through any part of the implementation in an interview.

What I'd do
differently

I built features instead of selling it.

The people I showed it to along the way loved it. But I got so focused on new features that I didn't sell it to anyone, and I haven't spent anything on ads. If I started again, I'd talk to detailers before building, launch a smaller product sooner, and put real effort into getting it in front of people from the start.

I'd also skip the route planner. Solo detailers usually clean three or four cars a day, so there is nothing there to optimize. It was a bad idea from the start, and I would never build it again.