Lessons from my third year running a SaaS
Max Rozen (@RozenMD) / February 23, 2024
It all started three years ago when I shipped a minimum viable product (MVP) in a week, at least, I thought it was minimally viable.
OnlineOrNot started as a toy - I had just learned how to use Next.js, and was wondering if it would be possible to build a SaaS app into my marketing website (it was, and honestly the benefit wasn't that huge compared to having them separate).
It wasn't until a few dozen releases later that folks started paying me for the product. That gave me the motivation to keep shipping. Feature after feature (released after 2 hours of work and iterated upon afterwards), the product became minimally-viable for more and more people.
Fast forward to now, I have customers, and have been growing at a steady rate while building the features customers ask for, but the product is no-where near complete, but I'm getting there.
Previous editions of this article:
Table of contents:
- I stopped being afraid of success.
- I ripped out my product analytics
- I experimented with onboarding, and it massively paid off
- I stopped trying to explain everything on one page
- I came crawling back to AWS.
- I started to understand my product philosophy
- To wrap this all up...
I stopped being afraid of success.
I originally doubted that being afraid of success is even a thing. I'd tell my self "of course I'm doing the best I can", while self-sabotaging my business in a dozen little ways that would compound into enormous effects.
Before this year, I would ignore and avoid:
- building tiny "table stakes" SaaS features (team management, on-call integrations, audit logs, etc)
- writing landing pages to explain the product
- making business decisions in general
At some point this year I had a realization that I was burning a lot of first impressions on incomplete software. My article writing was attracting curious people to try out OnlineOrNot, but I was losing them on missing features (and in a segment as competitive as mine, folks aren't often patient for incomplete software).
So I've calmed down a bit on the content marketing and immediately releasing software too early. I still release fixes and product improvements in 2 hour increments before work each day, but for completely new things, I wait until they're "ready" before promising something and under-delivering. I read somewhere that this is how 37signals built HEY Calendar - while they do 6-week cycles for improvements, for new products they run as many cycles as it takes.
I ripped out my product analytics
It turns out that as a small independent business I spend a good deal of my time actually talking to customers to figure out how it's going, learning what they expected when they signed up and what features are missing, rather than digging through analytics.
While I had analytics in the product, I wasn't using the data. So I ripped it out.
Me and other small business owners around me call it vibes-driven product management. It's not for everyone, but in my (obviously biased) opinion, I think I have good enough taste to pull this off.
That isn't to say I have no data. I still have anonymous cookie-less analytics of how many pageviews I get.
How it works
For the last three years, I've asked every single person that signed up to OnlineOrNot:
What's the one thing you were hoping OnlineOrNot could do for you?
Some folks respond, and those conversations have driven almost every feature and improvement that I've built since. While I have my own ideas and backlog, some of these conversations will make me realize I was thinking the wrong way about a feature, and that a drastically simpler implementation is possible.
I experimented with onboarding, and it massively paid off
OnlineOrNot's onboarding used to present users with a crucial question at the end:
Do you want to start a free trial?
Phrased like that it probably doesn't sound very crucial. What it really meant was:
Do you want to play around with OnlineOrNot's best features for 14 days before deciding if it's worth your time, or spend months with a product that has the good stuff disabled?
People did end up upgrading their subscription eventually from using the free tier exclusively, but it took significantly longer than folks that use the free trial version (it's a lot harder to see the value with half the features disabled).
So around May 2023 I decided to experiment with defaulting all users to a free trial first. This one experiment more than doubled OnlineOrNot's monthly growth rate.
It turns out starting the business relationship with "this is a paid service, you'll need to add payment details to continue getting the good stuff" helps the business significantly more than "this is a free service, if you use it a lot, you might have to pay for it".
I stopped trying to explain everything on one page
There's a saying that goes something like:
if you try to be everything to everyone, you'll end up being nothing to no one
I think it applies well to copywriting for landing pages.
As I built additional features into OnlineOrNot, I would try add additional sections to my main landing page, and it ended up an incoherent mess, diluting the overall message. I would have folks emailing me to ask if I supported sending alerts to Slack, when it was the second feature I built for OnlineOrNot.
Instead, by breaking up each feature into its own landing page (main landing page, uptime monitoring, api monitoring, status pages, cron job monitoring), I can take the space to explain each feature, without diluting the message.
I came crawling back to AWS.
I started OnlineOrNot on AWS Lambda, because I was never sure I would ever attract a customer. So if it had zero usage, it would cost me almost nothing per month to run.
I began evaluating other solutions after a particularly viral article sent hundreds of users to OnlineOrNot. My user base spiked, and so my AWS bill also spiked. I started by looking at fly.io, and moved my workload over. I liked it so much I even wrote about it.
Fly.io was an amazing user experience and a joy to use (especially the us-east region) and I still run a non-critical service there, but trying to run an uptime monitoring service on a platform that would regularly have network connection issues was a non-starter, and was beginning to hurt OnlineOrNot's reputation. It might make sense for running a web service, where a region's requests can failover to replicas, but it just didn't work for me.
I found myself needing to run each check several times in my VM, as well as backup checks on AWS AND Cloudflare to actually be sure my user's URL is actually down, rather than it being an issue with the VM I'm checking from. At that point I questioned why I even used fly.
I also tried running regular, boring VMs from Hetzner and DigitalOcean by refactoring my Lambda functions into a monolith (side note: vendor lock-in is a myth).
As a result of running OnlineOrNot in a single, ridiculously over-provisioned (yet significantly cheaper than AWS) server, uptime checks were randomly timing out, overall latency increased significantly, and made the overall system less reliable. Adding additional monitoring especially for VPSes would cost roughly how much I'd save by moving off AWS Lambda.
I spent a week tweaking code, increasing VPS size several times, trying different VPS providers, before eventually giving up, and deciding that paying a premium for AWS Lambda is worth it for the type of lifestyle business I want to run. Trade-offs and all that.
I started to understand my product philosophy
I read a quote this year from Justin Duke's mood board for his software business that resonated well with me and what I'm trying to do with OnlineOrNot:
We shall build good ships here
at a profit if we can
at a loss if we must
but always good ships
The business is bootstrapped and sustainable (it's default-alive), so I'm not too worried about profit. I'll still be here doing this in 10 years time. This year I realized there's nothing left to focus on except quality.
This year:
- I optimized my most frequently run SQL query, going from running for 5000ms every 15 seconds to 500ms
- I handrolled my own API documentation so that I could have full control of the product
- I fixed bugs that took 15 months to surface - a fun part of operating at scale is "one-in-a-million" bugs happen several times a day, though sometimes it takes a bit longer
- I finished two separate MVPs (status pages and cron job monitoring) that I started, so that I have a solid foundation to build a business on top of
- I started shipping JavaScript, and no one noticed - I started by not shipping JavaScript in the Status Page product as a mark of differentiation, but in the end it was just getting in the way of releasing features customers wanted
To wrap this all up...
Thanks for reading this, it's time to get back to shipping.