Skip to content
Engineering · 7 min read

What Running Our Own SaaS Taught Us

Operating two multi-tenant platforms changes how a studio builds: the second customer, upgrade paths, on-call, and what is actually worth alerting on.

The single most useful thing about operating your own software is that you cannot hand it to anyone. A studio that only builds is optimised for handover: the project ends, the repository is transferred, and whatever was awkward becomes someone else’s Tuesday. We run two SaaS platforms of our own alongside client work, which removes that exit and changes what gets built — not as a philosophy, but because the alternative keeps waking us up.

Here is what that has actually taught us, in roughly the order it hurt.

The second customer is architecturally harder than the first

The first customer is a demo with a login. Every value that varies between customers can still be a constant, and none of the decisions have been made yet — they have only been postponed, invisibly, into the code.

The second customer converts each of those constants into a column, and the list is longer than anyone expects: time zone, currency, tax treatment, business hours and holidays, branding, language, notification templates, which payment provider settles their money, which features they are allowed to see, and what their data retention rules are. Both of our platforms carry per-tenant branding and multi-language support for exactly this reason, and neither of those started as a feature request — they started as a constant that turned out to be a variable.

Two lessons generalise. First, the moment a value could plausibly differ per customer, it belongs in configuration rather than in code, even when there is only one customer, because moving it later means finding every place it leaked to. Second, configuration has to be reachable by a support person without a deploy. A setting that only an engineer can change is a settings screen with extra steps and a slower turnaround.

The related decision — whether those customers share a schema at all — is the one that is genuinely expensive to revisit, and we wrote about where each isolation model breaks separately.

You cannot ask version one to wait for version two

Nobody schedules a maintenance window across a whole tenant base. Someone is always trading. So an upgrade has to roll forward across every tenant while the platform stays up, which imposes a discipline that is easy to describe and easy to skip:

  • Expand, migrate, contract. Add the new column, write to both, backfill in batches small enough to not hold a lock, switch reads, then drop the old one in a later release. Three deploys instead of one, and no downtime in any of them.
  • Backfills are jobs, not migrations. Anything touching a large table needs progress, resumability and a kill switch. A migration that runs for nine minutes on your data will run for ninety on your largest tenant’s.
  • The migration must be observable per tenant. In a per-tenant-schema world especially, the state you care about is not “the migration succeeded”, it is “which tenants are on which version”, and there needs to be an alert for the ones that fell behind.
  • Old clients never go away. Both platforms publish branded iOS and Android apps, and you cannot make a customer update an app. A version you shipped a year ago is running in someone’s pocket right now, so the API contract is append-only in practice: add fields, never repurpose them, and never make an existing field mean something new. Repurposing a field is the change that looks free in review and breaks a store manager’s tablet in the middle of a rush.

On-call rewrites the definition of done

Once you carry the pager for your own architecture, “done” quietly gains four items, and they are the four things that get cut first on a project that ends at handover.

Structured logs with the tenant and the request id on every line, because the first question in every incident is “whose, and which one”. Metrics on the paths that carry money and orders, not just on the process. A runbook for each alert — one page, written by whoever built the thing, saying what the alert means and the first three things to check. And configuration and feature flags that a support person can change at 9pm without a release.

Three of those four are documentation and instrumentation rather than features, which is exactly why they lose the argument on a fixed-scope project with a launch date. They stop losing it the day you are the one being paged. Client work inherits that bias, and it is a large part of why every engagement closes with a warranty period rather than a handover email.

Alert on the business, not the box

CPU and memory alerts tell you a machine is unwell. They almost never tell you a customer is having a bad night. The alerts that have actually earned their place are shaped like sentences a support person would say:

  • Orders accepted but not printed or acknowledged by the point of sale, per merchant.
  • No inbound events from an integration during a merchant’s trading hours — the silence problem in POS sync, which raises no exceptions and shows up in no error tracker.
  • Payment failure rate rising on one provider or one region while the others are normal. When money settles across many regional networks, “payments are fine” is an average that hides the one market that is down.
  • Deliveries falling through to a third-party network more often than usual, which usually means an operator’s own driver roster is empty rather than that anything is broken.
  • Webhook processing lag and dead-letter depth, per integration.

Every one of those is a query against domain data rather than infrastructure telemetry, which means they only exist if someone decided the domain was worth instrumenting. That decision is cheapest during the build and most obvious during an incident.

Money needs a reconciliation job, not a dashboard

Anything involving payments, refunds or third-party settlement needs a scheduled process that compares two systems and reports the difference — not a screen that renders your own database back to you, confidently.

The failure this catches is the quiet one. A refund issued in your platform but never accepted by the provider, an order captured but voided at the till, a payout total that disagrees with the order total by the value of a tip added after close. None of these throw exceptions. They surface weeks later as an accounting question, and by then the evidence is spread across three systems and one memory. Reconciliation is how you find them the next morning instead.

The same argument applies to any two-writer system, which is why we treat it as the default posture for integration work rather than a specialisation.

What clients get from this arrangement

The honest version: not magic, just fewer experiments. When we argue for a tenant column in the first migration, an admin surface in an MVP, or an integration budget that is per vendor rather than per project, it is because the other version of that decision already cost us something on software we still maintain. Estimates for multi-tenant SaaS work come off subsystems we own and operate rather than off an analogy.

It also sets what “long-term” means. Code written in a hurry on our own products, we maintain; abstractions invented to save a week, we live inside. That is a better teacher than any review process, and it is the standard the client work is held to. If you would rather read the company facts plainly than the argument, they are all on one page.

If you are about to start operating your own product

Five things worth doing in the first month, all of which are cheap then and expensive later:

  1. Put a tenant id on everything, including logs, metrics and cache keys.
  2. Make every configurable value configurable now, in a place support can reach.
  3. Write the first runbook before the first incident, even if it has one entry.
  4. Add one business-level alert per critical flow — orders, payments, integrations — and make it page a human.
  5. Restore a backup, on purpose, before you need to.

None of it is difficult. All of it is much harder to add while a customer is waiting for you to fix something else.

Keep Reading

Ready to Build Something That Ships?

Tell us what you're building. We'll get back to you within 24 hours with a clear plan and an honest estimate.

Free consultation · No upfront costs · Reply within 24 hours