What building four side products taught me
Over the past few years, I've launched four side products into the wild: SEOlenz, Expona, QRzeo, and Giftcey. None of them became unicorns. All of them taught me something that I couldn't have learned from building internal platforms or reading documentation. What building four side products taught me is that the gap between "technically possible" and "actually useful to real people" is where engineering craft really matters.
Ship the smallest useful version — Each product started with one core feature
The biggest mistake I see in side projects is treating them like production platforms from day one. Teams spend months architecting microservices, planning scalability, designing perfect databases—before a single real user touches the product.
With SEOlenz, I started with something embarrassingly simple: a CLI tool that scraped keyword data and printed it to stdout. No dashboard. No authentication. No database. Just a script that solved one problem for one person (me). Within two weeks, a handful of people asked for the same thing. Within a month, I had enough signal to justify building the next layer.
Here's the pattern I've repeated across all four products:
- Week 1: Core logic in the simplest form. A script. A single endpoint. A static site.
- Week 2-3: Package it so someone else can use it without me explaining it in person.
- Week 4+: Listen to what breaks, what confuses people, what they ask for next.
For Expona (an event management tool), the MVP was a single form that let you create an event and share a link. No real-time updates. No fancy dashboard. No integrations. But it worked, and it let me watch how people actually used the tool.
The math is simple: shipping fast means learning fast. Every week you delay shipping is a week you're guessing instead of knowing.
Infrastructure is a product skill — A tunnel platform taught me DNS, proxies and SSL deeply
QRzeo started as a URL shortener with custom branding. But early users kept asking if they could use it to create branded download links without hosting servers themselves. That request morphed into what became a tunnel platform: a way to expose local services to the internet through branded domains.
Here's where I realized something crucial: infrastructure knowledge isn't just about DevOps. It's a product skill. When you're building infrastructure-adjacent features, you need to deeply understand DNS, TLS certificates, proxy behavior, and networking—not as an afterthought, but as a core product requirement.
I had to learn:
- How DNS propagation timing affects user experience. TTL mismatches caused real confusion.
- SSL certificate provisioning and renewal. Expired certificates meant unhappy users.
- Proxy pass-through behavior. Headers get mangled in subtle ways that only show up under real load.
- Rate limiting and abuse prevention. Without it, one user's misconfiguration takes down everyone's tunnel.
This wasn't theoretical knowledge. Every lesson came from a user hitting an edge case or misunderstanding how the system worked. I built monitoring dashboards specifically to watch certificate expiry, DNS propagation, and proxy latency. I added error messages that explained what was happening, because users needed to understand the infrastructure concepts to use the product correctly.
Here's a simplified example of something I built to surface infrastructure issues to users:
interface TunnelHealthCheck {
dnsResolvesCorrectly: boolean;
certificateValid: boolean;
certificateExpiresIn: number; // days
proxyLatencyMs: number;
lastCheckedAt: Date;
}
async function checkTunnelHealth(domainName: string): Promise {
const dnsCheck = await resolveDomain(domainName);
const certCheck = await verifyCertificate(domainName);
const latencyCheck = await measureProxyLatency(domainName);
return {
dnsResolvesCorrectly: dnsCheck.success,
certificateValid: certCheck.isValid,
certificateExpiresIn: certCheck.daysUntilExpiry,
proxyLatencyMs: latencyCheck.latency,
lastCheckedAt: new Date(),
};
}
This health check became a core product feature, not a debugging tool. Users needed to see it because understanding infrastructure problems is part of understanding their own product's reliability.
Billing and auth take longer than expected — Every SaaS needs them. Reuse what you can
I've implemented authentication and billing four times now. Every single time, I underestimated how long it would take.
Giftcey (a gifting platform) is probably my best example here. I estimated two weeks to add Stripe billing and user auth. It took five weeks. Not because the core logic is complex—it isn't. But because of all the things that sound simple until you actually do them:
- Handling failed payments and retry logic
- Webhook signature verification (Stripe sends webhooks, you have to validate they're actually from Stripe)
- Refund flows and subscription cancellation edge cases
- Rate limiting on auth endpoints so you don't get DDoS'd
- Password reset flows with token expiry
- Session management and logout behavior across multiple devices
- Compliance stuff: storing minimal PII, audit logs for payment events
Here's my learned practice: don't build these from scratch unless you have a specific reason. Use what exists.
For authentication, I now reach for Auth0, Clerk, or for simpler cases, managed solutions from your cloud provider. For billing, Stripe's ecosystem is mature enough that custom logic is rarely worth the effort. What you actually want to customize is business logic—how users are metered, how discounts work, how trials behave—not the payment processing itself.
But here's the catch: integrating these services means understanding their APIs deeply. Failed webhook handling has caused more revenue loss than any other issue in my products. Stripe's retry logic is good, but not perfect. You need monitoring.
// Webhook handler for subscription updates
async function handleStripeWebhook(event: Stripe.Event) {
try {
if (event.type === 'customer.subscription.updated') {
const subscription = event.data.object as Stripe.Subscription;
await updateUserSubscriptionStatus(subscription.customer as string, subscription);
}
if (event.type === 'invoice.payment_failed') {
const invoice = event.data.object as Stripe.Invoice;
await notifyPaymentFailure(invoice.customer as string, invoice);
// Critical: log this for manual follow-up
await logPaymentFailureForSupport(invoice);
}
} catch (error) {
// If webhook processing fails, we need to know immediately
await alertOncall('Stripe webhook processing failed', { event, error });
throw error; // Let Stripe retry
}
}
The key point: billing isn't a feature you ship and forget. It's ongoing operational work. Every product I built that skipped proper billing monitoring eventually had revenue leaks I only discovered weeks later.
Users teach you — Real usage changed every roadmap I had planned
I went into each of these four products with a roadmap. I was wrong about the roadmap in all four cases.
SEOlenz was supposed to be a keyword research tool with competitor tracking. Users wanted it as a monitoring dashboard that alerted them when rankings changed. Different product.
Expona was supposed to be a ticketing system. Users wanted it for internal meetups and free events. It became an event calendar tool.
QRzeo was supposed to be a QR code designer. It became a tunnel platform because that's what people actually paid for.
Giftcey was supposed to be a marketplace. It became a corporate gifting workflow tool when our early users were all HR teams.
The pattern is clear: what you think your product is and what it actually becomes are often different things. The successful moves were the ones where I noticed what users were actually doing and leaned into it, rather than defending my original vision.
This requires real feedback loops. Not surveys—actual usage data. Session recordings. Support tickets. Which features do people get confused about? Which ones do they use repeatedly? Which ones are you explaining over and over in onboarding calls?
The features users love solving for themselves are the features that matter. Everything else is noise.
Common mistakes
Building too much before shipping: I've scrapped more code by shipping early than by planning well. The code you write before users see it is often wrong.
Treating side projects like your day job: This kills momentum. Your day job has deadlines, processes, documentation standards. Your side project has exactly one goal: does someone want this enough to keep using it? That's a different skill.
Ignoring operations from day one: Logging, monitoring, alerting—these feel premature when you have five users. But your fifth user will have problems you can't debug without them. Build observability in from the start. It's not overhead; it's how you learn.
Rewriting instead of iterating: When you see how users actually use your product, the temptation to redesign everything is high. Resist it. Small changes to address real problems beat architectural rewrites every time.
Key takeaways
- Ship the MVP in a week or two, not a month. You learn more from one week with real users than from four weeks of planning.
- Infrastructure knowledge compounds fast when you're responsible for end-to-end reliability. Push yourself to understand DNS, networking, and certificate management at a deep level.
- Billing and auth are necessary, not exciting. Reuse proven solutions and spend your energy on your actual product differentiation.
- Build with real-time feedback loops: session recordings, usage analytics, support tickets. Your roadmap should change monthly based on what you learn.
- The best products often solve a different problem than the one you set out to solve. Stay flexible enough to notice and pivot when users show you what they actually need.






Comments
Loading comments…