
Flyout
Screen-edge notes for macOS
I sell a paid Mac app directly, no App Store, and no server of mine that the customer ever talks to. Payment goes through Stripe, the license is signed and emailed automatically, and the app verifies it offline. Total infrastructure: one Cloudflare Worker, one SQLite database on D1, and Amazon SES for the email.
The pipeline, in order:
Stripe Payment Link. No custom checkout page.
Stripe fires checkout.session.completed at the Worker.
The Worker signs a license key with an Ed25519 private key held as a secret.
It writes a row to D1: email, key, session id, payment intent id.
SES sends the key to the buyer.
The app verifies the signature locally against an embedded public key. No call home, works on a plane.
That is a few hundred lines. The interesting part is that all three bugs I hit were silent. Nothing threw, nothing showed up in logs, and the happy path kept working while specific customers got nothing.
Bug 1: checkout.session.completed does not mean you were paid.
It means checkout finished. With delayed payment methods the session arrives with payment_status 'unpaid' and the money settles days later, or never. Deliver straight off the event type and you hand over the product before you have been paid. The fix is one line: check payment_status explicitly and refuse otherwise. I found this reading the docs while chasing something else, not because anything broke.
Bug 2: The Stripe SDK does not work on Workers out of the box.
Its default HTTP client is Node's http module, which does not exist in the Workers runtime. You have to pass httpClient: Stripe.createFetchHttpClient(). Miss it and you fail at request time, in production, inside the webhook handler, which is the one place nobody is watching.
Bug 3: SES's suppression list drops mail without telling you.
If an address is on your account level suppression list, SendEmail still returns a MessageId and a 200. Your code believes it sent. Nothing sent. In my case the address had been on that list for years because of one stale bounce from a completely different context. So the first thing I check now when someone says the email never arrived is the suppressed destination for that address, with get-suppressed-destination in the sesv2 CLI.
The other piece worth stealing: make delivery idempotent and call it from two places. My fulfillment function runs from the webhook and also from the thank-you page, which asks for the receipt using the session id. Whichever arrives first wins, and the race is settled by a UNIQUE constraint on the session id in D1. The webhook can be slow or fail outright and the buyer still sees their key the moment they pay. Before that, a late webhook looked exactly like a broken purchase to someone who had just handed me money.
The theme, if there is one: with no backend there is nothing to monitor, so every failure mode I hit was something that returned success and quietly did nothing. Every guard I have added was for a bug that produced no error.
For anyone running something similar: what else silently succeeds in your stack? I would rather add the guard now than hear about it from a customer.
About
I kept losing thoughts between windows. Closing what I was doing to go find a notes app took long enough that the thought was gone. I wanted the editor to come to me instead.

5 Comments
What I found interesting is how many failures weren't technical failures at all—they were verification failures.
A system can report success at every step while the outcome the customer actually cares about never happens. Those are often the hardest problems to notice because nothing appears to be broken.
Yes, and the ones that get me are where the signal itself looks honest. My widget spent weeks showing an old note: the app called the refresh API on every change, the OS was quietly dropping those calls once past its daily budget, and nothing on my side ever saw a failure. What broke it open was writing a visible marker into the data and watching the surface the user actually sees, instead of trusting anything the sending side reported.
That's a great example. The distinction between verifying the system's action and verifying what the user actually experiences is exactly the part that caught my attention.
I'd be interested in continuing the conversation by email if you're open to it. What's the best email to reach you on?
Sure, gorkem [at] depfloy.com works. Happy to keep going there, the "verify the surface, not the sender" habit turned out to apply to more than just the widget.
Thanks! I’ve just sent it over.
Looking forward to hearing your thoughts whenever you have a chance.