Guides

Building a SaaS product: what we learned building and running Onrolo and GoRizzume.

By Zain Masroor · 14 October 2026 · 12 min read

Build the parts a SaaS product cannot cheaply retrofit first: customer data kept apart in the database, sign-in and roles, billing driven from the same usage record as the product, and background jobs that can safely run twice. Add mobile apps and AI features once those hold, and measure every AI feature’s cost per customer from its first release.

Talk it through: book a free 30-minute call
Get right firstTenancy, sign-in, billing
Most often missedJobs and webhooks that can run twice
AI ruleCost per customer, per feature

The two products this comes from

We built and run two SaaS products of our own. Onrolo is an applicant tracking system for UK recruitment agencies, on web, iOS and Android, used by agencies with several offices and teams. GoRizzume is a careers platform for UK students and graduates, on the web and as iOS and Android apps. Both are written in TypeScript and Next.js on Postgres, and both bill through Stripe.

Everything below is something we either got right early and were glad of, or got wrong and fixed. None of it is exotic. It is the plumbing that decides whether a SaaS product can be trusted with customers’ data and money.

Multi-tenant data: draw the boundary on day one

A SaaS product serves many customers from one codebase and usually one database, so the first design decision is how each customer’s data is kept apart. Every row that belongs to a customer carries that customer’s identifier, and the rule that one customer can never read another’s data is enforced in the database itself, with Postgres row-level security, not only in application code where a single missed filter leaks data.

Two practices have paid for themselves many times. First, an automated test that signs in as one customer and tries to read another’s records, run on every change. Second, feature switches per customer organisation: we turn new features on for one organisation first, watch it, and then widen it, so a pilot never means a release to everyone.

01Customer identifier on every customer-owned table, from the first migration.
02Isolation enforced in the database, with a cross-tenant read test in the test suite.
03Staff access to customer data through an audited admin view, not direct database access.
04New features switched on per organisation before they are switched on for all.

Sign-in, roles and every domain you serve

Use a proven authentication provider rather than writing password handling yourself, and model roles per organisation from the start: the same person can be an admin in one customer and a viewer in another. Invitations, password resets and removing a leaver are the flows customers test first, usually without telling you.

One lesson we learned the hard way: when a product is served on more than one domain, every security check that looks at the request’s origin has to know about all of them. We once had a product accept reads but refuse every change on a secondary domain, because the origin check listed only the primary pair. The fix was a proper same-origin check rather than a hard-coded list.

Billing with Stripe: one record of usage

Let Stripe hold subscriptions, invoices and payment methods, and let your product hold usage. The rule that matters is that the invoice is generated from the same record of usage the product shows the customer. When usage is counted in one place and invoiced from another, the two drift, and the drift is always in the customer’s favour.

Stripe’s own documentation says webhook endpoints might receive the same event more than once, and that events are not guaranteed to arrive in the order they were created, with retries for up to three days. So every webhook handler records the event IDs it has processed and does nothing on a repeat, and every payment button is protected against a double click. Test the first invoice, a trial ending, an upgrade, a failed card and a refund against real test accounts before switching billing on.

Background jobs and email

Anything slow or external belongs in a background job: sending email and messages, generating documents, calling a model, syncing with another system. Each job should be safe to run twice, because retries will run it twice, and should record what it did so a failure can be traced. Scheduled jobs need the same care, plus an alert when one stops running.

Two small traps from our own code. Comparing timestamps after they have passed through a JavaScript date loses precision, so a job that fetches records newer than the last one it saw can pick up that same record again; exclude by ID, not by time. And email deliverability is a product feature: authenticate the sending domain, keep transactional email apart from marketing, and watch your complaint rate in Gmail’s Postmaster Tools, because a provider that starts filtering your messages takes password resets down with everything else.

Mobile apps: web first, then native

Both our products started on the web and added iOS and Android apps once customers wanted them. A responsive web app reaches the same users for a fraction of the cost; native apps earn their place with notifications, offline use, the camera and a home-screen presence. When they come, the API they use has to keep serving older app versions, because users do not all update on the day you release.

Store releases carry their own overhead: Apple charges $99 a membership year and Google Play a one-time $25 registration, each release goes through review, and signing is fragile. Enabling a new app capability, such as notifications or sign-in with Apple, invalidates the provisioning profile, and an automated build will fail until it is regenerated. Budget a day for the first release on each store, and keep the steps written down.

AI features and what they cost to run

An AI feature has a running cost per use that a normal feature does not, and in a product priced per seat a heavy user can cost more than they pay without anyone noticing. So every model call in our products is attributed to a customer and a feature, and every customer has a spending ceiling that has been triggered on purpose and watched stopping work before anyone relies on it.

The small model does most of the work. At Anthropic’s September 2026 list prices Haiku 4.5 is $1 per million input tokens and $5 output, against $2 and $10 for Sonnet 5. In our own products, moving one high-volume feature from the frontier model to the small one made it several times cheaper and faster with no drop in success. Route each step to the smallest model that passes your own test.

Keeping it maintainable

A SaaS codebase outlives its first developer, so build it for the next one. That means boring, well-supported technology, every database change as a migration in version control, error tracking and product analytics from the first release, and a test or preview environment that is not production. It also means one change per pull request, so a problem can be traced and reverted without undoing unrelated work.

Write down the decisions as well as the code: why tenancy works the way it does, how billing is reconciled, how to release to each store. And switch off what you no longer use. Old projects, environments and integrations keep billing quietly long after anyone remembers them.

01TypeScript end to end, Postgres for data, Stripe for billing, hosting you can move away from.
02Migrations, not hand edits, for every database change.
03Error tracking and analytics live before the first customer.
04A runbook covering releases, billing checks and incident steps.
05A quarterly sweep for unused infrastructure and keys.

Common questions

What should I build first in a SaaS product?

The parts that are expensive to retrofit: customer data kept apart in the database, sign-in and roles per organisation, billing driven from the same usage record the product shows, and background jobs that are safe to run twice.

How do you keep customers’ data apart in a SaaS product?

Put a customer identifier on every customer-owned row and enforce isolation in the database, for example with Postgres row-level security, then run an automated test that tries to read another customer’s records on every change.

What are the common mistakes with Stripe billing?

Counting usage in one place and invoicing from another, and handling webhooks as if each event arrives once and in order. Stripe says events can be delivered more than once and out of order, so record event IDs and ignore repeats.

Should a SaaS MVP have mobile apps?

Usually not at first. A responsive web app reaches the same users for less. Add native iOS and Android apps when notifications, offline use or device features earn them, and keep the API compatible with older app versions.

How do you control the cost of AI features in SaaS?

Attribute every model call to a customer and a feature, set a spending ceiling per customer and test that it fires, and route each step to the smallest model that passes your own test on real examples.

What tech stack do you use for SaaS products?

TypeScript and Next.js on the web, Postgres for data, Stripe for billing, and native iOS and Android apps where a real app is needed. It is the stack we run Onrolo and GoRizzume on.

What does it cost to have a SaaS product built?

We build SaaS products at £8,000 to £30,000 fixed per phase, with a startup MVP at £10,000 to £20,000. The code and accounts are in your name from day one.

Read next

More guides: Building software and MVPs →SaaS development in London →MVP development in London, £10k to £20k fixed →Bespoke software development in London →Our prices, in full →What an AI feature costs to run →Case study: Onrolo →

Building a SaaS product?

We build on the same stack and the same rules we run our own products on, fixed price per phase, with the code and accounts in your name from day one.