The best decision in my first AI-built SaaS was deleting two days of work
What three weeks of building Owly Post actually looked like, as part of my 90 day AI challenge
A few weeks ago I committed to a 90 day challenge where I build with AI every day to develop an AI workflow you can copy. I wrote about the why and how in the first post. Part of the challenge was to build a real, working SaaS product with AI. This week, I launched it. It's called Owly Post, and it's the first product I've ever shipped end to end myself.
For the last few weeks I've been trying to get better at building with AI, and Owly Post is how I put that to the test in a real environment: does my workflow still hold when an agent writes the code, and do I stay the one deciding when I'm not the one typing?
For anyone who's been following me, you've seen the name pop up plenty of times in my daily notes. Today I want to dive in properly and share what I've built these last few weeks, and what happened along the way.
This is about how I built the first working version of the cloud from a single prompt. About deleting two days of finished, working code because it had turned my open source project into a dependency. And about launch week showing me everything I'd missed, and what I'm not good at.
Meet Owly Post: not my best idea, the one I actually shipped
Owly Post is a tool that takes all the feeds you follow (Substacks, YouTube channels, podcasts) and turns them into a single digest, sent on your own schedule. Based on your preferences, it recommends what's worth reading and flags what isn't important. So you can get up to speed in about five minutes by reading just the summary, or read through everything it recommends and skip what isn't important to you right now. And it learns from what you like and read, so those recommendations keep getting better.
It's for anyone who follows more feeds than they keep up with, and would rather read one good summary on a fixed moment than nothing at all.
I built it in two layers. The first is an open source core: free, and yours to run yourself if you want to. The second is a cloud version on top of that same core, for anyone who'd rather not set any of that up themselves. You sign up, connect your feeds, and the digest just shows up. Same product underneath, without the work of hosting it.
I’m aware this isn’t the most ambitious or ground-breaking project. It doesn’t solve a big problem beyond my own enjoyment of it (I genuinely struggle to make time for learning and reading). But that was the point. I’ve wanted to build a SaaS for years, and every time, I waited for the perfect idea, the one big enough to justify the effort. The waiting was the problem. High expectations blocked me every single time.
So this time I deliberately picked something small: a tool I actually wanted, that didn't need to be impressive. I didn't have to interview a single potential user, I was the user. It's nothing more than a learning project, and I'm figuring it out as I go.
Alright cool beans, now show not tell.
Two layers, one core
If you’re curious about the stack: the core runs on Next.js, TypeScript, and PostgreSQL, and ships as a Docker Compose setup, one command and it’s running. It stays complete on its own, and nothing about it depends on the cloud existing.
The cloud layer runs on Netlify, with Supabase for the database, Resend for email, and Stripe for billing. It doesn’t add any product functionality the core lacks, because it runs the exact same core underneath. What it adds is convenience: no Docker, no LLM keys, no setup, it just works out of the box.
I’ll probably swap out Netlify and Supabase later. But, cheapskate that I am, I wanted to be able to start for free.
The cloud is built on the core. It wraps the untouched core and builds the cloud application around it. That was the hardest architectural puzzle: taking something designed as a single-user install and making it serve many users at once.
The open source part
Three days into the challenge, on 1 July, I started working on the open source repo, not knowing exactly what I wanted to build. By the end of that same day the app worked: a storage layer, Supabase integration, feed ingestion, AI summaries, weekly digest, a scheduler, a semi decent UI, and documentation. All in one day.
A week later, I learned that open source works a little differently from private projects. Choices I could make once for myself, based on my preferences, suddenly needed to work for anyone who might run this. So that week went into refactoring and reworking the Docker Compose setup, and turning the LLM and mailer from hard dependencies into configuration choices: bring your own key, bring your own mailer.
And then it kept going. Deduplication so the same article doesn't show up twice across feeds, protection against malicious feed links, a scheduler that doesn't skip your week when it runs late, a feedback loop where rating a section steers the next digest, and a cleanup job that clears out old article text.
I didn't write any specs upfront, and I let myself get carried away, just vibing. It builds so easily that you keep saying yes, and you end up with a feature list you never actually decided on. Which is funny, because I'm a product manager myself, and I'd have killed my dev team for doing exactly this.
It taught me exactly what to do differently on the cloud.
The cloud part
Because of this I built the cloud the opposite way: spec first, the way I usually work. Before a single line of code existed, on the 8th of July, I wrote two documents: a SPEC with the business decisions, the database schema, who gets access to what in which subscription state, and a handful of rules I wasn’t allowed to break. And a detailed kickoff prompt that pointed the coding agent at all of it.
The rules were simple. Things like: every database query goes through one layer that automatically limits it to a single user, so I can never accidentally show one person someone else's data. Every limit, how many feeds, how much AI budget, gets answered by one function instead of scattered checks across the code. And a stop rule: anything I hadn't thought of, the agent had to stop and ask me about, instead of quietly improvising. My answer then became a numbered decision.
Then I split the build into phases: the core foundation, serving many users at once, accounts and billing, onboarding, and hardening.
Part of this was a deliberate experiment. Anthropic's Fable 5, the most capable model available to me, was temporarily included in my subscription at no extra cost, and that window closed on the 12th of July. So on the 9th I handed it all over as a single prompt: the spec, the rules, the phases. And let it build. I spent the whole weekend going back and forth over the stop rules, finishing it just in time.
The joke was on me: I spent the entire weekend racing a deadline, and the next day they extended it anyway. I put the extra time to good use regardless.
The first working version of the cloud came out of that one prompt. I can imagine that sets off alarm bells. But the prompt was the shortest part of the whole thing. It took a full day to get there: fifteen decisions locked down one by one, access rules per subscription state, the schema, the stop rules, and the phasing that told the model what to build in what order and where to pause. That's not one instruction, that's a full brief. The prompt just pointed at it.
Somewhere in that same week, to let the cloud reuse the open source project, I first restructured that project so it would fit neatly around the hosted version. It worked perfectly, but anyone setting up the open source version now got a pile of extra machinery that only existed to make the cloud's life easier. So I threw away two days of finished, working code and went back. Still the right call: the open source version stayed something you can use on its own, instead of a leftover of the cloud.
After that came the part that actually took time. A design system to replace the deliberately ugly first screens. A serious pass on auth. The onboarding rebuilt from scratch, because I chose a different direction. And a lot of testing.
Here we are: it's live
Owly Post is out in the world. Not a prototype, not a demo behind a waitlist. A thing you can actually use, today, that sends real emails.
Three weeks ago this was an idea I’d been putting off for years, because I had a million excuses not to build it. It wasn’t a cool enough idea, I didn’t know how, maybe someone else had already made it better. And now it's here. I'm proud of it, especially this early into the challenge.
Two ways to use it. Run the open source version yourself, free, on your own machine. You can find the open source core here. Or use the cloud version, where you sign up, connect the feeds you already follow, pick a schedule, and the first digest shows up in your inbox. That one lives over here, cute little owl included.
Here’s what it looks like.
The cloud version is slow. It runs on free-tier hosting, which was a deliberate choice: I wanted to see whether this could exist without costing me anything to run. I went into this as a learning project, so paying for a plan felt like the wrong instinct to start with. So if you take a look, apologies in advance for the loading times.
The launch
This week I spent my time getting the product live. In the three days around it four things came up, mostly the kind you only find, of course, once it’s running somewhere other than your own machine.
First, a deploy issue. The build platform shipped the piece that connects the cloud to the core, but not the core behind it, so every function that needed it died at startup. Quietly, because the platform confirms the request before the function actually runs. From the outside everything looked fine while nothing worked.
Then the first trial user showed up and did what I never did: behave like someone who isn’t me. Four feeds, and the system happily pulled their entire backlog and summarized every article in it. There goes the trial’s monthly AI budget, in fifteen minutes. A feed limit wouldn’t have saved me either, because one feed can be ten articles and another a thousand.
After that, my own digest didn’t arrive. The scheduler only sends when a check runs inside your scheduled slot, and it wasn’t running often enough to hit it. Nothing flagged it either, because every check I had was looking for things that failed, and a digest that never gets sent leaves nothing behind to fail.
And then an audit found that the endpoints that send out digests and clean up accounts could be called by anyone who knew the URL. Which is exactly as bad as it sounds.
All four map neatly onto the things I knew least about: deployment, cost control, and infrastructure security. Which is also why none of them were in the spec. You can only write down decisions about things you know exist. So they turned up as problems instead. Each one meant reading up on something first, then fixing it properly once I understood what I'd actually done. You still need to know what's going on, otherwise it slips right past you.
The honest retrospective
What went well. The boring stuff. I wrote things down before I built them, and made the agent stop and ask instead of guess, so I stayed the one deciding even when I wasn't the one typing. And at one point it just felt off, every step needing another round of explaining what I actually meant, so I trusted my own judgement and threw the whole thing out. Looking back, that was the right call.
What needs work. Sequencing. I built the open source part on vibes and paid for it in rework. I wrote down the rules about ownership only after I’d broken them. And I saved the live walkthroughs for last, even though every one of them produced a decision I’d have wanted earlier.
What’s next
Is it finished? No. I release things before they're ready on purpose, because that's the only way to find out what actually needs improving. So I'll keep working on:
The landing page. I’ve always wanted to build a properly animated, interactive site, so that’s what I’m going to do;
Learning and improving by actually using it myself, and from other people's feedback. So if you have cool ideas, feel free to send me a message;
New features. By now I have a backlog full of ideas, though I’m not sure yet what I’ll build next, that depends on the feedback too.
Keeping it running and improving it. It's a product now, not a weekend project, which means it needs attention and maintenance.
The deep dive, if you're interested
From here on it gets somewhat technical. The story is done, so feel free to stop here. If you want to see how it's actually held together, keep going.
The wrong version worked perfectly
Here's one way of building an open core, and how I built it first: you extract the shared logic into a package. I restructured the open source repo into a workspace, carved out @owlypost/core as an npm package, set up a publish pipeline.
Then I sat with it for a day and saw what I’d actually done. Anyone cloning the public repo now got a monorepo with a packages directory and a publish workflow, none of which they needed to run the thing. All of that existed for one reason: to make my hosted version’s life easier. That’s how open core quietly goes wrong. Every change you make to the open project for the cloud’s sake looks reasonable on its own. Until the free version isn’t a real standalone project anymore.
So I reverted all of it, two days of work, and the open source repo went back to being exactly what it was. The cloud version needs to bend around the core, not the other way around.
Vendoring the untouched thing
The cloud still needed a way to run the core, without a package and without changing anything in the open source repo. What I ended up with is almost embarrassingly low tech. The cloud repo pins the unmodified public repo as a git submodule and puts one small bridge package in front of it, the only code in the entire cloud repo allowed to import from it.
Three rules, written down where my tools and I can’t pretend not to see them:
The core is never changed for the cloud’s sake. Core changes are maintainer decisions, made on the core’s own merits, on the public repo.
Upgrades are one submodule bump, with the full test suite proving it safe.
No fork, no copies, no reimplemented core logic in the cloud. If the cloud needs something the core doesn't offer, it composes around the core, or it waits.
This came with two trade-offs. I can now only measure AI usage per digest run instead of per call. And a speed improvement I'd already made got thrown out with the revert, so I had to build it a second time, on the core itself.
Running a single-user app for many users
The hardest thing for me: how do you serve many users (multi-tenancy) from an app that was built for exactly one, without modifying it?
It already had the two openings I needed, because I'd specified them that way from the start. The core doesn't talk to a database directly: it gets handed the thing that reads and writes for it. And it doesn't have its settings baked in either, it reads them at the moment it runs.
So the cloud runs the core once per user. Before each run it hands over a version of that reader and writer that can only ever see one user's rows, and it sets that user's settings for the length of the run. The core genuinely believes it is still serving one person. It just gets told, very carefully, which person, one run at a time.
Guarding all of it is the test I care most about in the whole codebase: two users, the full pipeline for both, then proof that neither one read or wrote a single row belonging to the other. Nothing touching storage, jobs or auth ships without it passing.
What I found digging
Going back over the security side, I found that any signed-in user could change their own account settings and hand themselves free unlimited service, because one database rule was more generous than it should have been. The isolation test I was so proud of had stayed green the entire time, because it checked whether users could reach each other's data, and this was someone reaching above themselves. Your best test defines the exact shape of your blind spot.
That's the deep dive. Three weeks in, one product live, and a much better idea of what I don't know yet and still need to improve. On to the next one.




