When "Skip" Quietly Means "Never Again"
Bakery Buddy has an automation called Birthday Rebooking. Once a year, it looks at a customer's past birthday cake order and drafts an email asking if they want the same thing again. It should be one of the easiest wins in the whole product. A parent who ordered a birthday cake last year is exactly the person who needs one again this year.
For a while, at Lindsay's studio, it drafted nothing.
The question that surfaced it
The question was simple: were there really no birthday orders worth emailing? Clicking "Run matches now" produced nothing, and the approval inbox sat empty. It read exactly like the honest answer was no.
It wasn't. There were eight matching orders sitting right there in the data. Four separate bugs were stacked on top of each other, and each one hid the next.
Bug one: a skip that never expires
The automation stores every drafted email in a table with a rule meant to stop duplicates: don't draft the same email for the same order and the same step twice. That rule is correct for a one-time email, like a review request sent once after an order is picked up. It is wrong for anything recurring, because it never asked which year.
So when a baker declined a birthday draft, a normal, everyday "not this time," the slot for that order filled up forever. Not just for this year. Every year after it too. The automation had already drafted seven real emails weeks earlier, tied to $835 of real past orders. Those got skipped. After that, the order was permanently unenrollable, and nothing in the product could explain why.
The fix was to give every draft a cycle key, the anniversary year it belongs to. A skip now only blocks that one year. Next year is a fresh slot.
Bug two: an anniversary that only fires once
The second bug computed "next birthday" by adding exactly one year to the order date, one time, and never recalculating after that. Any order older than about thirteen months had already sailed past its one shot and would never come up again, this year or any year after.
Real numbers made this one impossible to ignore: 33 of 85 birthday orders in the studio's history, representing $6,427 in past order value, were sitting in that dead zone. Not edge cases. A third of the list.
Bug three: a send time computed from the wrong year
The scheduler was reading the raw original order date to decide when to send the reminder, instead of the anniversary date the matcher had actually computed. That meant some drafts got a send time a full year in the past, which pushed them to the top of the approval inbox and kept them there, permanently "overdue," for a birthday that hadn't happened yet.
Bug four: nothing told anyone any of this was happening
This is the one that made the first three invisible for weeks. The matching logic dropped candidates in four different places without reporting any of it, and the preview screen wrapped its own failures in a catch block that quietly fell back to the same message as "still loading." A real bug, an empty result, and a page still fetching data all looked identical on screen.
The part worth keeping
None of these four bugs are specific to birthday cakes or email automations. They're the same shape you'll hit in any system that's supposed to repeat:
- A uniqueness rule meant to stop duplicates needs to know what actually repeats. "Once per order" is not the same guarantee as "once per order, per cycle."
- A person declining something once should not quietly mean they declined it forever, unless that's genuinely what you're building.
- Date math that only fires in one direction, add a year, once, needs a floor and a repeat rule, or it silently expires and never comes back.
- A silent catch block is a decision to make "broken" and "empty" look identical. That decision is almost never the right one. Count what you drop, and say so on screen.
The fix got checked against the studio's live data: eight real candidates worth $1,270, zero dropped, and a roughly 280-case sweep of the new date logic, including a leap-day edge case the tests actually caught. None of that would have mattered if the tool had just kept quietly reporting zero.
If anything in your product is supposed to happen again next year, go check what "no" actually means the first time someone says it. You can see how Birthday Rebooking and the rest of Bakery Buddy's automations work today.
Frequently Asked Questions
What was the actual bug?
Birthday Rebooking's duplicate-prevention rule stopped a customer's order from being emailed twice, but it didn't know a birthday repeats every year. A single skip filled that order's slot permanently, in every future year, not just the current one.
Did this affect a real bakery?
Yes. Before the fix, 33 of 85 birthday orders at Marin Cake Studio, representing $6,427 in past order value, could never be re-enrolled by the automation. After the fix, running the matcher immediately surfaced 8 real candidates worth $1,270.
Why did the tool say there was nothing to send instead of showing an error?
Four separate failures were stacked together, and one of them was a catch block that silently fell back to the same screen as "still loading." A real bug, an empty result, and a page still fetching data were indistinguishable, which is worse than any one of them alone.
What's the general lesson for anything recurring?
Any uniqueness or dedupe rule needs to know what actually repeats, not just that something happened once. And any place your code silently drops or swallows a result should count what it drops and say so, instead of quietly returning a clean-looking zero.
I build Bakery Buddy for my wife Lindsay's cake studio, Marin Cake Studio, where every automation gets tested against real orders before anyone else sees it.
Ready to put this into practice?
Join the Waitlist