How I Would Build a SaaS - Tenancy, Billing, and What Makes It a Product
An MVP can be a form. A SaaS is a product many companies rent. How I would design tenancy, subscriptions, onboarding, and ops - and what I would not copy from big platforms in year one.
An MVP answers: will anyone finish this job? A SaaS answers: will many companies keep paying so we can run one codebase for all of them? That second question is not “add Stripe and a login.” It is tenancy, billing, onboarding, and a way to help a stranger at 2 a.m. without SSHing into their data.
I already wrote how I would pick a stack for an MVP. This is the next layer: when the shape of the business is software-as-a-service, what I would actually build, in what order, and what I would refuse to copy from Linear or Salesforce in month one.
1. First: is this actually SaaS?
A lot of “SaaS ideas” are a custom system for one client with a monthly invoice. That can be a good business. It is not SaaS. SaaS means one product, many tenants, self-serve or light-touch sales, and the cost to add the next company is close to zero - not another project.
- SaaS: many orgs, same features, billing in the product, you own the uptime.
- Custom software: one buyer, unique workflows, you invoice for change requests. Do not pretend the second client will “just log in.”
- If the first three customers each need a fork, stop calling it SaaS. Sell projects until the overlap is obvious - then extract the product.
2. The unit of the product is the workspace, not the user
I would model the domain around an organization (workspace, tenant, account - pick one word and keep it). Users belong to orgs. Data belongs to orgs. Bills belong to orgs. If you start with “User has Projects,” you will spend six months bolting on teams, invites, and “who pays.”
- Org has a plan, a billing email, and a status: trial, active, past_due, canceled.
- Membership is a row: user + org + role (owner, admin, member is enough at the start).
- Every business table has org_id. Queries without org_id are bugs, not shortcuts.
- Invites before “create user for them.” Email in, accept, land in the right workspace.
3. Tenancy: shared database, strict isolation
For a first SaaS I would not open a Postgres per customer. I would use one database, org_id on every row, and defense in depth so a missed WHERE cannot become a data leak. Database-per-tenant is for regulated isolation or noisy neighbors you have already measured - not for tenant number four.
- Row-level security or an equivalent query helper that always injects org_id from the session - not from the client body.
- IDs that are not guessable (UUIDs). Sequential /org/12/invoices/4 is an invitation.
- File storage prefixed by org_id, signed URLs, never a public bucket of “all uploads.”
- Schema-per-tenant or DB-per-tenant only when a contract, a compliance checklist, or a real noisy neighbor forces it. Migrations across 200 schemas are a product you did not want.
4. Billing is a feature, not a plugin you add later
If nobody can pay, you do not have SaaS. I would put Checkout in the critical path in week two, even if the rest of the app is thin. A “we will invoice manually” phase is allowed for the first five design partners - then the card has to work.
- Stripe Billing (or the processor your first market actually uses): products, prices, customer portal, webhooks. Do not store card numbers. Do not write a dunning engine.
- One or two plans, not seven. A trial with a clear end. After trial: read-only or hard block - pick one and say it in the UI.
- Price a thing customers understand: seats, projects, or monthly volume. Usage billing is powerful and easy to get wrong; start with a seat or a flat plan until you know what “usage” even is.
- Webhooks are the source of truth for “are they paid?” Your database mirrors Stripe (or Paddle). Cron that guesses is how you lock a paying customer out.
5. The stack I would use (and how it differs from a site MVP)
The web default still holds: Next.js, TypeScript, Tailwind, Postgres. SaaS adds a few pieces that a marketing site does not need - and still does not need a mesh of services.
- Auth that understands orgs: Clerk, WorkOS, Auth.js plus your membership table - not a single global “logged in” flag.
- Background jobs on day one if anything is slow or retryable: emails, Stripe webhooks, exports, LLM calls. A simple queue (Inngest, Trigger.dev, or a worker on the VPS) beats a setTimeout in a serverless function.
- Transactional email from minute one: invite, receipt, “your trial ends Friday.” If that mail does not arrive, the product feels dead.
- Feature flags for plans (can_export, seat_limit), not if (org.plan === "pro") scattered in fifty files.
- Hosting: Vercel plus hosted Postgres is fine until a worker or residency says otherwise. One region is enough. Multi-region is a later problem.
6. Onboarding is the product for the first hour
Empty SaaS dashboards do not convert. I would design the first session as a job, not a tour: create the workspace, import or type the first real record, invite one teammate, see one result. Tooltips over a blank table are not onboarding.
- A sample workspace is allowed if it is clearly fake and one click away from “start with my data.”
- Activation metric: they completed the core job once, not “signed up.” Track that. Pitch that.
- Settings can be ugly. The empty state of the main screen cannot.
7. Ops: how you will support people you have never met
The day a paying org writes “I cannot see my invoices,” you need a path that is not production SSH. I would add a thin internal admin before the tenth customer, not after the first incident.
- Admin: find org, see plan and last webhook, impersonate read-only, resend invite. Audit that impersonation.
- Logs with org_id and request id. “It failed” without a tenant is not a log line, it is a shrug.
- Backups you have restored once. Nightly is enough at the start; untested backups are a story you tell yourself.
- Status page can wait. An email “we are down, here is what we know” cannot.
8. What I would not build in year one
Big SaaS is a museum of features that paid for themselves later. Copying the museum is how you miss the one job.
- SSO, SCIM, and a 40-page security questionnaire until a real deal is blocked on them. Then they become the sprint, not a side quest.
- A public API and a marketplace of integrations. One Zapier or a CSV export often unblocks the same buyer.
- Per-tenant custom domains, white-label, and a theme studio. A logo upload is enough for most early B2B.
- AI everywhere. One place where language is the job (search, drafts, extraction) with a schema and a billable meter. Not a chat bubble on every screen.
9. A sequence I would actually follow
Same calendar honesty as an MVP, with SaaS-shaped milestones. If a week has no tenant, no pay, or no job, it is not a SaaS week.
- Week 0: name the job, the buyer, the plan, the kill number (e.g. five paid orgs in 90 days or we stop). Non-goals on a page.
- Weeks 1-2: org + membership + one happy path in production + Stripe test mode. Isolation tests: user A must not see user B.
- Weeks 3-4: live billing, invites, emails, empty-state onboarding, thin admin. Put it in front of named companies, not “the internet.”
- Days 30-90: watch activation and churn reasons. Add the one integration or report they pay for. Do not add a second product.
Conclusion: rent a job, not a platform
I would build SaaS the same unglamorous way as an MVP, plus the parts that make many companies share one system: a workspace, a bill, a wall between tenants, a first hour that is not empty, and an admin so support is not a root shell. The stack can stay boring. The product cannot stay vague.
If you have a workflow several companies already pay you to repeat, and you want it as a product instead of a pile of custom deploys - write via the contacts section. We can name the tenant model, the first plan, and a slice that takes a card without pretending to be Salesforce.
Need this built, not just explained?
Web app development: Next.js, React, PostgreSQL. Direct contractor.
Ready to discuss your project?
I'm a senior web engineer specializing in React and Next.js - available for freelance projects worldwide.
Location
Kyiv, Ukraine
Upwork
View ProfileTelegram
Contact meViber
Contact me