Cantürk Çelikok
All postsProduct Development

Taking a SaaS from MVP to Production: My Checklist and Lessons

Cantürk Çelikok • • 6 min read

Taking a SaaS from MVP to Production: My Checklist and Lessons

The MVP has done its job. Now what?

An MVP has one job: to answer the question "Will anyone actually use this?" When the answer is yes, every shortcut that let you build the MVP quickly turns into debt overnight. Hard-coded settings for one client, scripts run by hand, error logs nobody looks at... None of it mattered with the first client. With the fifth, each one means a separate late night.

PlusTurk also started with the needs of a single law firm and then grew into a platform serving clients in different industries. During that transition I built myself a checklist. In this post I share it, along with why each item is there. They aren't all big jobs; some take an afternoon. But none of them is small enough to say "we'll look at it later".

1. Data model: who does each row belong to?

This is the most important question before going live. One customer's data showing up for another is one of the worst things that can happen to a SaaS product, and it usually comes from a filter forgotten in a single query.

  • Every table that holds customer data has a tenant column, and it can't be empty.
  • Queries don't add the tenant filter one by one; the data access layer adds it automatically. I don't leave something that can be forgotten up to a developer's memory.
  • Unique fields (such as a file number) are unique per tenant: two different firms should be able to use the same file number.

I covered the mistakes here in detail in 5 common mistakes in multi-tenant SaaS architecture. If you didn't build this structure in the MVP, building it before going live costs a fraction of building it afterwards.

2. Identity, roles and permissions

An MVP usually has the rule "a signed-in user can do anything". With a real client, though, the firm owner, the lawyer, the intern and the accountant shouldn't see the same screens.

  • Roles are small and clear from the start: who can see what, who can change what.
  • Permission checks don't stop at hiding a button in the UI; every API endpoint does its own check on the server.
  • Two-factor authentication is mandatory for admin accounts.
  • If I need to get into a customer's account for support, it's done through a "view as user" feature and every use is recorded in the activity history. No shared passwords.

3. Schema changes: reversible migrations

In an MVP it's normal to drop the database and recreate it. In production that option doesn't exist. Every schema change will be applied to a table with real data in it.

My rule is expand–contract: if a column needs renaming, I first add the new column, the app writes to both for a while, I migrate the data, and I only drop the old column once no code reads it. Each step is a separate release. It looks slow, but no step ever requires stopping the app or doing something you can't undo.

4. Taking backups isn't enough; try restoring them

Almost everyone takes backups. Very few people have tried restoring one. An untested backup is a backup that might not work when you need it.

  • The database is backed up automatically every day, and the backups are kept somewhere other than the main server.
  • Every so often I restore a backup to an empty environment and check that the app starts with it.
  • Files customers upload (documents, receipts) are backed up separately if they aren't part of the database backup.

5. Observability: see the problem before the customer does

In an MVP, when something breaks the user phones you. In production most users don't call; they just stop using it.

  • Server errors go to an error tracking tool, and I get a notification when a new type of error appears.
  • Logs are structured (JSON) and every line has the tenant and a request ID. "What happened to this customer at this time?" can be answered with a single search.
  • A simple uptime check running from outside makes sure I notice the site is down before the customer does.

6. Background jobs and external services

Jobs such as sending SMS, handling payment notifications and generating reports should run in a queue, not inside the request. If pressing a button sends an SMS to five hundred people, the page shouldn't wait until that's finished.

  • Every call to an external service has a timeout and a limited retry policy.
  • Retried jobs are idempotent: the same SMS isn't sent twice, the same payment isn't processed twice. I covered the payment side in payment integration best practices.
  • Jobs that keep failing don't silently disappear; they go to a separate list where they can be inspected by hand.

7. Limits against abuse

The login form, password reset and every endpoint that sends SMS are doors someone malicious can use to run up your bill. SMS verification endpoints in particular can cost a lot in a few hours if there's no limit.

  • Login and password reset have attempt limits per IP and per account.
  • Endpoints that send SMS and email have daily limits per user and per tenant.
  • File uploads are checked for size and type on the server.

8. Environments and secrets

  • Development, staging and production use separate databases and separate API keys. An SMS going from the staging environment to a real customer is an accident that happens more easily than you'd think.
  • Passwords and API keys live in environment variables, not in the code repository.
  • How to rotate a key if it leaks is written down, and it can be done from one place.

9. Subscription and billing states

The code for a paid product isn't just "paid / didn't pay". There are customers whose trial has ended, whose card was declined, who cancelled but are entitled to use it until the end of the period. What the product does in each of these cases needs deciding up front; otherwise each one turns up as a separate support ticket.

10. Data protection and the data lifecycle

  • Every form that collects personal data has a privacy notice ready and shows it to the user (in Türkiye, the KVKK notice).
  • It's clear how long each type of data is kept, and there's a deletion or anonymisation job for data past its retention period.
  • How a departing customer's data is handed over and deleted is defined.

The short list

A one-page summary of everything I check before going live:

  • All customer data is tied to a tenant and the filter is automatic.
  • Roles are defined, permissions are checked on the server, admins have two-factor authentication.
  • Migrations are reversible and done in small steps.
  • Backups are automatic and restoring has been tested.
  • Errors are tracked; logs carry the tenant and request ID.
  • Long jobs are queued; external calls have timeouts and are idempotent.
  • Login, SMS and email endpoints have limits.
  • Environments and keys are separate.
  • Product behaviour is defined for every subscription state.
  • Privacy notices and retention periods are in place.

The whole list is a few weeks' work. But every item prevents a midnight phone call in production, or a lost customer. If you're getting a SaaS product ready for launch, I describe how I run this process in more detail on the SaaS development page.

Cantürk Çelikok

Freelance full stack developer based in Ankara, Türkiye. He built live products such as PlusTurk, Nerdesin, Ayda POS and Songnaria Coffee end to end, and shares the notes from those projects here.

About me