Why we gave every client their own database

One Supabase project per client, not one shared database for everyone. Isolation and a full export are why it was the right call.

An admin approves every new account by hand. Nothing is created until then. We reply by email; no newsletter, no sequence.

app.salescrew.io/today
The daily working view

The short answer

  • SalesCrew runs single-tenant. Each client instance gets one Supabase project, with Postgres, storage and edge functions. It's not one shared database with a customer ID column.
  • We carry the heavier setup. You get a wall that doesn't depend on one access rule staying correct.
  • What it buys you: isolation built into the structure, not a policy. A full database dump as your export, not a few CSVs. And a region you choose per client when it's set up.
  • The same design is what makes snapshots work cleanly. One client's stages, cadences and agent policy carry over to the next instance.

The cheaper default, and why most SaaS CRMs pick it

Almost every SaaS product, CRMs included, runs multi-tenant. One database and one set of servers serve every customer at once. A customer ID column keeps rows apart, and the app decides which rows each request may see. That's the cheaper design to run. The vendor scales one system, not one system per customer. So it's the default almost everywhere.

SalesCrew doesn't run that way. Each client instance gets its own Supabase project, with its own Postgres database, storage and edge functions. That's a heavier choice for us to run at scale. We made it anyway. A shared database can't offer two things by its structure: isolation and a full export. Those matter more to a CRM than to most kinds of SaaS.

What a database boundary gives you that a permission rule cannot

In a shared database, rules keep customers apart. Those are row-level security policies, filters in the app, and careful queries. The rules can be right for years and wrong once. When they're wrong once, one customer's rows show up for another. There was never a hard wall between them. A rule stood in for one.

A separate database per client removes that risk by design. No query can return another client's rows by accident, because those rows aren't in the database being queried. SalesCrew still applies row-level security inside each client's project. It also scopes access on three axes: channel, Upwork profile and area. A locked building still has locked rooms inside. The project wall and the RLS policy do different jobs, not the same job twice.

Export is the other place this shows up. A shared-database vendor can hand you CSVs of the objects it chose to expose. It can't hand over the database, because that database also holds every other customer's data. A single-tenant database has no such limit. Your export is a full Postgres dump plus CSVs everywhere, because nothing else lives in that project.

On cancellation, SalesCrew gives read-only access, then export. Deprovisioning comes after 90 days, as a step a person triggers, not an automatic delete. We can offer that safely because your data was never mixed with anyone else's.

What this made possible beyond isolation

The same project-per-client setup makes data residency a real choice, not a marketing line. Setting up a client means creating a new Supabase project, so you pick the region at that moment, per client. It isn't one region the whole platform runs in, which every customer inherits wherever their business works.

It also supports a feature that wouldn't make sense in a shared-database product: snapshots. A snapshot captures one client's setup as an object you can export: its pipeline stages, cadences, dispositions and agent policy. You then apply it to another client's instance. That works cleanly because each instance is its own project with its own settings. Applying a snapshot means setting up a project, not filtering rows in a database everyone shares. If you're an agency with several client instances, this is how "we set up one client well" becomes "we set up every client the same way."

A database per client is heavier to run than a shared one, and SalesCrew carries that load. Our product is built around agents writing to customer data and agencies reselling configured instances. For that, the trade points toward a wall that doesn't depend on a rule staying correct forever.

Questions

Isn't row-level security enough to isolate customers in a shared database?
Row-level security is a real control, but it's one policy away from a mistake. In a shared database, a single loose or wrong RLS rule can expose rows across every customer at once. The wall is a rule the app must get right. It isn't a physical split between databases.
Does a database per client change what a client pays?
No. SalesCrew publishes one price per instance with unlimited seats: Core is $299 a month. Every tier includes the dedicated Supabase project, and every tier includes the full Postgres export.
Can data residency actually be chosen per client this way?
Yes. Each client's data lives in its own Supabase project, so you choose that project's region when it's set up. It isn't one shared setting that applies to every customer on the platform at once.