Skip to main
Omar Nagy.
← writing·Receipts·~9 min read·filed 2026-06-10

2,831 orders on 30 commits: the boring system a donut shop kept over the better one I built

My family's donut shop has taken 2,831 orders through a 30-commit system I stopped touching in March. I built the far more capable replacement myself, migrated the full order history into it, and the shop never switched: 1,012 more orders on the old system since, with zero code changes.

2,831
orders · 30 commits · 7 tables
  • receipts
  • shipped
  • legacy-systems
  • adoption
  • small-business
  • retailos

My family runs a donut shop in a small Egyptian city. Since October 2025 the shop has taken 2,831 orders through a small web system I wrote, and the system is not the interesting part. The interesting part: I later built a far more capable platform, migrated the shop's entire order history into it, and the shop never switched. The old system has processed 1,012 more orders since my last commit to it. Zero code changes. Zero maintenance.

This is the receipt for that. The build (third attempt, repo to first real order in under 24 hours). The life (eight months of near-daily orders on 7 database tables). The twist (the better system lost). And the lesson I now apply on paid work.

Two disclosures up front. Mr. Donut is my family's shop. I did not win this customer, I was born into the account, so read this as a production case study and not a sales reference. There are no revenue figures in it. Second: every number below comes from read-only SQL on the production database, plus the GitHub API for repo history, pulled June 10, 2026, with daily and monthly buckets in Cairo time. When you wonder how a claim was measured, that is the answer: it is a query, and I can rerun it.

00 · The receipt

Lifetime, October 4, 2025 through June 9, 2026, which was yesterday as I write this:

  • 2,831 orders, with orders on 226 of roughly 249 calendar days since launch. That is about 91%, so the honest adverb is "near-daily," not "daily." June so far is literally daily: orders on all 9 of the first 9 days, 152 orders, 16.9 per day, peak day 29.
  • Roughly 1,300 distinct customers. Precisely: 1,295 distinct phone numbers. The system has no customer accounts, so a phone number is the closest thing it has to a customer ID. 533 distinct numbers ordered within the last 90 days.
  • 96.7% delivered. 2,737 of 2,831 orders ended in "delivered." 89 were canceled, 3.1%. And 98.7% of orders are deliveries rather than pickups. This is a delivery business with a counter attached, not the reverse.
  • 7 database tables. Next.js 14.2, React 18, Supabase, Tailwind. 17 delivery zones. 3 staff accounts. That is the entire inventory of the system.

Seven tables do not leave much room for ceremony, and the system spends none. Orders come in, get assigned to a zone, get delivered, get marked. That is the whole job.

01 · Two dead prototypes, then 24 hours

This was the third attempt at getting software into the shop. The first two are part of the receipt, because the failure count is the number that usually gets edited out of essays like this one.

Express + Vite prototype

aug 2025 · 8 commits

Built, pushed, demoed, abandoned. It never made it into the shop's daily routine. The commit log just stops.

Next.js PWA admin

sep 2025 · 6 commits

A second swing one month later, this time as an installable admin app. Same ending, two fewer commits.

The one that stuck

oct 2025 · 30 commits

Prototyped in v0.dev first; the package name in package.json is still "my-v0-project," and I am leaving it that way. Repo created October 3, 2025. First production order from a real customer: 23 hours and 58 minutes later.

I want the speed claim stated precisely, because "built a production system in a day" is the kind of line this industry inflates. The repo went from created to processing a real customer order in under 24 hours. The UI had been prototyped in v0 before the repo existed. So the accurate sentence is: from repo to first real order in under 24 hours, on top of a prototype. Still fast. Not magic.

After that first day: 30 commits total, spread between October 3, 2025 and March 4, 2026. Five months of occasional fixes, then nothing, while the orders continued.

I cannot prove why attempts one and two died; the logs just stop. What is measurably different about the third is that it took a real customer order on day one. After that, the shop had a reason to keep using it, and I had a live system to fix instead of a prototype to polish. My read, not a controlled experiment: shipping into real use within a day is what broke the prototype loop.

02 · Eight months of orders

// mr. donut · orders per month

0219437orders390Oct437Nov397Dec332Jan247Feb282Mar340Apr254May152Jun*oct 2025 to jun 2026 · *jun = first 9 days only
Monthly order counts from read-only SQL on the production database, Cairo timezone, pulled 2026-06-10. June covers only June 1 to June 9.
oct 2025 to jun 2026 · *jun = first 9 days onlyorders
Oct390
Nov437
Dec397
Jan332
Feb247
Mar282
Apr340
May254
Jun*152
Monthly order counts from read-only SQL on the production database, Cairo timezone, pulled 2026-06-10. June covers only June 1 to June 9.

The shape is a real business, not a launch spike. November is the peak month at 437 orders, and the single busiest day on record is November 10, 2025, at 34 orders. February is the floor at 247. Spring recovers: 282, then 340, then a soft May at 254. June is running hot so far, 16.9 orders per day against a 10.3 average across the last 90 days. I am not projecting a month from nine days; I am noting the shop is busy.

The last 90 days are the recency check on all of this: 924 orders, orders on 72 of those 90 days and on 21 of the last 30, from 533 distinct phone numbers. The customer base is not five relatives placing sympathy orders.

Operationally the numbers stay boring in the right way. 96.7% of lifetime orders delivered. 3.1% canceled. Three staff accounts run the whole thing, and the delivery radius is encoded as 17 zones. Nothing in this paragraph would survive a keynote, and it does not have to. It has to survive a Friday rush.

03 · The twist: I built the replacement

While the donut system sat unchanged, I built RetailOS, my multi-tenant retail platform. It is more capable than the donut system on every axis I cared about as an engineer. It is also, for this particular shop, the system that lost.

On March 4, 2026, two things happened on the same day. I made my final commit to the donut shop's repo. And a Mr. Donut tenant was created in RetailOS, with the shop's order history migrated in: 1,918 orders at migration time. The data work was clean. The monthly counts inside RetailOS match the legacy database exactly: 437 for November, 397 for December, 332 for January, 247 for February.

Two facts about those 1,918 orders, stated as plainly as I can. They are copied history, not adoption; nobody at the shop placed an order through RetailOS to create them. And they are where the story of that tenant effectively ends: RetailOS has not recorded a single new Mr. Donut order since March 24, 2026.

The legacy system's record over the same period reads differently.

1,012
orders since the final commit · zero code changes
The last commit to the legacy repo is March 4, 2026. Every order after it ran on unmaintained code: 746 of them since April 1.

No deploys. No dependency bumps. No hotfixes. I had the better system, the data migrated and verified down to matching monthly counts, and the shop kept the worse one. Looking at the last 90 days of order flow, the shop was right.

04 · Why the worse system won

My explanation, flagged as interpretation rather than SQL.

The old system fits exactly one business. Seven tables, 17 zones, 3 accounts. Every screen exists because this specific shop needed it. A multi-tenant platform has to fit many businesses at once, which means each individual business meets it partway: more concepts, more configuration, more screens that exist for someone else's use case.

Switching costs are real even at family scale. The staff already knows where every button is. A migration asks them to relearn the muscle memory that gets them through the evening peak, in exchange for capabilities (roles, analytics, tenancy) that the person boxing donuts at 9pm cannot feel. I could not name a benefit they would feel at the counter. That should have told me the outcome before the order counts did.

"Done" is a real state. The industry treats unmaintained code as a liability by definition, and for large systems it usually is. But a small, well-scoped system with seven tables and one job can genuinely be finished. This one has been finished since March 4 and has processed 1,012 orders since, including orders on 21 of the last 30 days. Done is not the same as abandoned. The difference is whether it still does its one job, and the production database keeps answering yes.

To be clear about what this essay is not: RetailOS is not dead, and this is not a case against platforms. A different family shop runs RetailOS natively. Lolies, a kids' fashion store in the Nile Delta, has put 204 orders through it between May 2 and June 10, 2026, including one today. The difference between the two shops is the whole lesson. At Lolies, RetailOS is the system. At the donut shop, it was a replacement for a system already doing the job. A replacement carries a burden of proof that a first system never faces, and "more capable" does not meet it.

05 · The boring version is the product

What I carry from eight months of watching this, into work people pay for:

Scope to the counter, not the category. The donut system is not "retail software." It is Mr. Donut software. That is why 7 tables were enough, why a v0 prototype could go from repo to first real order in under 24 hours, and why the 30th commit could be the last. I applied the same scoping rule on purpose when I built Crema, a cafe operating system I stood up in a day: the fast builds are not fast because of heroics, they are fast because the scope is one real counter. The longer version of that argument is the Harmonia 14-day writeup.

Measure adoption, not capability. I shipped a capable platform and verified the migration. I never measured the only thing that decides a cutover, which is whether anyone at the counter wants to switch. The cheapest time to learn that is before you build the replacement. I learned it from a read-only SQL query, months after.

Let small things be finished. Five months of commits bought eight months and counting of unattended operation. For software this size, maintenance is not a virtue. It is a cost, and the best systems I have shipped for small businesses are the ones that stopped needing me.

Most businesses that ask me for software do not need the most capable version of the thing. They need the boring version that matches how they actually work, shipped fast enough to take a real order the same week, and scoped tightly enough to be done. If you are weighing "keep the boring system" against "replace it with the capable one," that is a call worth making with data instead of instinct: Find the leak is one week on your stack, a plain-English plan, and the first fix shipped. The donut shop taught me which side usually wins.

// next move

Want this level of rigor on your own stack?

Find the leak: 1 week, $950, fixed scope. A plain-English plan plus one real fix built and working, yours to keep regardless.

// related essays