
Why We Rolled Our Own
On evaluating SaaS options, building with Supabase + Stripe + Resend, and the 4-hour experiment that changed everything
At 8:46 AM on February 27th, 2026, I shipped a full Stripe payment system for this blog. Monthly subscription tiers. Webhook handlers. A customer portal for managing billing. The works.
By 12:43 PM the same day — three hours and fifty-seven minutes later — I ripped it all out.
This is the story of that decision, and why I think every indie publisher should own their subscriber list.
The SaaS Evaluation
When I started insights.codes, I did what everyone does: I looked at the existing tools.
Buttondown was my first choice. Clean interface, good markdown support, reasonable pricing. But I'd be renting my subscriber list. If Buttondown changes their pricing, adds features I don't want, or shuts down — I'm migrating.
Ghost is beautiful but it's a whole CMS. I already have a Next.js site that I love. Adopting Ghost means either migrating everything or running two systems. Neither is appealing.
Substack is the elephant in the room. Great for discovery, terrible for ownership. You're building on someone else's platform, subject to their algorithm, their design choices, their business model. When Substack makes a controversial decision, you eat the reputational hit.
ConvertKit (now Kit) is the most flexible option. But at $29/month for basic features, the economics don't make sense for a blog that might have 500 subscribers in its first year.
The common thread: every SaaS option means someone else holds my subscriber data. Someone else controls my relationship with my readers.
The Build-It-Yourself Stack
The alternative was surprisingly simple.
Supabase for the database. Free tier gives me 500MB of storage and 50,000 monthly active users. For a subscriber table, that's essentially infinite. Row-level security means the data is locked down from day one.
Resend for transactional email. Free tier gives me 3,000 emails per month. For a blog publishing weekly to a few hundred subscribers, that's plenty. Their API is clean and their deliverability is excellent.
Stripe for payments. 2.9% + 30 cents per transaction. No monthly fee. No platform lock-in.
Total monthly cost: $0.
Not "free tier that'll cost money later." Zero dollars. Supabase and Resend both have free tiers that scale well beyond what a small blog needs. Stripe only charges when someone pays.
Compare that to $9-$29/month for any SaaS option, plus the hidden cost of platform dependency.
The 4-Hour Experiment
The morning build went fast. Claude Code and I had the whole system running by 8:46 AM:
- Three subscription tiers ($5, $10, $25/month)
- Stripe Checkout with a customer portal
- Webhooks for subscription lifecycle events
- Database columns for
stripe_customer_id,stripe_subscription_id, andsupport_amount_cents
It worked. Technically, it was solid. But as I tested the flow — click subscribe, enter card details, manage billing in the portal, handle cancellations — I realized something.
I was building complexity I didn't need.
Monthly subscriptions mean:
- Webhook handlers for
invoice.paid,invoice.payment_failed,customer.subscription.deleted - A customer portal so people can update their cards and cancel
- Failed payment retry logic
- Dunning emails for expired cards
- Proration calculations for plan changes
- State management for active/past_due/canceled/trialing
All of that operational overhead for a blog where the primary product — the writing — is free. The paid tier was a nice-to-have, not the core offering.
So at 12:43 PM, I committed fix: patch subscribe P1s and remove Stripe payment system and stripped it all out.
What We Learned
The 4-hour experiment taught me three things:
1. Start with what you need, not what you might need. The blog needs free email subscriptions. It does not need a billing system. Building the billing system first was solving a problem I didn't have.
2. Recurring billing is a product, not a feature. Handling subscription lifecycle — upgrades, downgrades, cancellations, failed payments, card updates — is its own product. Bolting it onto a blog adds maintenance burden that compounds over time.
3. One-time is enough. If readers want to support the blog, a one-time donation through Stripe Checkout is all we need. No webhooks for subscription events. No portal management. No dunning emails. One endpoint, one event type, done.
That's what we built instead. The donate section on our subscribe page uses a single Stripe Checkout session in mode: "payment" with submit_type: "donate". The button says "Donate" instead of "Pay." The entire server-side code is about 40 lines.
The Architecture
Here's what the final stack looks like:
Subscription Flow:
User submits email → /api/subscribe (rate limited)
→ Supabase upsert (RLS, service role only)
→ Resend confirmation email
→ User confirms → localStorage flag set
Donation Flow:
User picks amount → /api/checkout
→ Stripe Checkout (mode: payment, submit_type: donate)
→ Stripe webhook → Supabase upsert (support_amount_cents)
→ /thanks page
The subscription flow has zero external dependencies beyond Supabase and Resend. The donation flow adds Stripe, but only for one-time payments — the simplest possible Stripe integration.
Database schema is a single table:
subscribers:
id, email, status (pending/confirmed),
stripe_customer_id, support_amount_cents,
confirmed_at, created_at
The stripe_customer_id and support_amount_cents columns sit there quietly until someone donates. No wasted complexity.
Why Ownership Matters
When you use a SaaS newsletter platform, you're one API change away from a migration. When you use Substack, you're one controversy away from a brand problem. When you use a managed service, you're one pricing change away from a cost surprise.
When you own your subscriber list in your own database:
- You control the data. Export it, query it, join it with other tables, run analytics.
- You control the experience. Custom emails, custom confirmation flows, custom landing pages.
- You control the cost. Free tiers on Supabase and Resend mean $0/month for years.
- You control the future. Want to add paid content later? The infrastructure is already there.
The Supabase free tier alone handles more subscribers than most indie blogs will ever have. And if you outgrow it, you own the schema — migrating to any Postgres host is a pg_dump away.
The Reading Meter
One more thing we built: a 5-article reading meter.
After reading 5 unique articles, the content blurs and a subscribe overlay appears. Enter your email and the counter resets immediately. Confirmed subscribers never see it again.
It's all localStorage — no server-side tracking, no cookies, no fingerprinting. If someone clears their storage to read more, good. They're engaged enough that we want them reading.
The meter isn't DRM. It's a gentle nudge at the moment someone has gotten enough value to justify sharing their email. Medium used this exact model for years.
Should You Build Your Own?
If you're running a small blog or newsletter and you're comfortable with basic web development, yes. The tools have gotten too good to pay $29/month for something you can build in a day.
The total effort to build the subscription system for insights.codes — including the 4-hour Stripe detour — was less than two days. And now I own every piece of it.
If you found this useful, subscribe for free to get posts like this in your inbox. And if you want to support the blog, there's a donate option at the bottom of the subscribe page.