Skip to content
Book a technical review
Aerial night view of a desert town: a grid of low buildings lit by scattered warm orange street lighting, with a single arterial road running out into empty dark desert

Building a multi-tenant field-service SaaS that onboards a new company in under a day

Softwise built a multi-tenant field-service SaaS for an operations company in Riyadh. One codebase serves every customer through a shared-schema Postgres design with row-level security, Moyasar payments and Wafeq invoicing per tenant, and an onboarding flow that provisions a new company in under a day instead of a multi-week manual setup.

Context

The client and the problem

Anonymized by design: industry and city, not names. The shape of the problem is the point.

The client

An operations company in Riyadh dispatching field technicians for maintenance contracts. They resold their internal dispatch tool to two partner firms by cloning the codebase, and were maintaining three diverging copies. Every bug was fixed three times. Adding a fourth customer meant a fourth copy.

The problem

Cloning an app per customer is the most common way a working product turns into an unmaintainable one. The real requirement was one product, many isolated customers: separate data, separate branding, separate billing, one deploy.

The constraints

Saudi infrastructure only. Data residency and the client's own compliance posture required hosting inside the Kingdom, and payments had to run through Moyasar — no international gateway detours. Stripe was never on the table.

Three live customers on day one. The two partner firms could not tolerate downtime or a data migration that changed how their dispatchers worked. The consolidation had to happen underneath them, invisibly.

No dedicated ops team. The client has no infrastructure staff. Whatever was built had to be deployable, backed up, and observable by the two engineers who maintain it part-time.

Build

What we built

Decisions, not adjectives. Each one had an alternative we rejected.

Shared schema, enforced isolation

One Postgres database with a tenant_id on every row and isolation enforced by row-level security policies at the database layer, not in application code. A missed WHERE clause in a query cannot leak another company's data, because the database itself refuses to return it.

Tenant provisioning as a product feature

New-company onboarding became a form, not a project: company record, subdomain, branding, seat limits, a Moyasar payment profile, and a Wafeq invoicing account created in one transaction. Setup that previously took weeks of manual work now completes in under a day, most of it waiting on the customer's own data import.

Billing that matches the plan

Per-seat subscription billing with proration and dunning collected through Moyasar, the Saudi payment gateway, with invoices issued through Wafeq and reconciled nightly against the tenant registry so finance and the database never disagree about who pays for what.

Per-tenant branding and domain handling

Each tenant gets its own subdomain, logo, and color scheme resolved at the edge from the hostname, before application code runs. A dispatcher at acme.app and one at globex.app see two different products that happen to share one deployment.

Operated by two, not twenty

Migrations run automatically on deploy, and background workers handle everything that must not block a dispatcher's request: subscription charges, dunning retries, and the nightly reconciliation that compares the tenant registry against Moyasar and Wafeq. Alerting ships to the small internal team. The architecture assumes no on-call rotation exists, because for this client it does not.

Multi-tenant field-service SaaS: full architectureTenants reach one production deployment through their own subdomains. An edge domain resolver maps each hostname to a tenant_id before application code runs. Inside one deployment: the Next.js and Node.js application, a branding service resolving per-tenant themes, a PostgreSQL database with tenant_id on every row and row-level security enforcing isolation, background workers for billing and reconciliation, Moyasar collecting per-tenant payments, and Wafeq issuing ZATCA-compliant invoices. Nightly reconciliation treats the database as the tenant registry against both providers. Hover or tap a component to trace its connections.ONE PRODUCTION DEPLOYMENT — HOSTED IN-KINGDOMrequestrequestrequestsignuptenant contextresolve themebrandingqueries + tenant_idfiltered queriesown-tenant rows onlyjobschargesdunningnightly reconciliationissue invoicepayment stateprovision tenant + profilespayment profileinvoicing accountTenant acme.appRiyadh operations companyTenant globex.apppartner firmTenant partner.saanother partnerNew tenantonboarded in under a dayEdge domainTLS · subdomain → tenant_idBranding servicelogo · theme · seat limitsApplicationNext.js + Node.jsBackground workersbilling · dunning · reconciliationRow-level securitydatabase-enforced isolationPostgreSQLshared schema · tenant_idTENANT REGISTRYTenant onboardingone transactionMoyasarpayments · mada · Apple PayWafeqZATCA e-invoicesnightly: PostgreSQL (registry of record) ↔ Moyasar ↔ Wafeq
Full architecture of the multi-tenant field-service SaaS Softwise built in Riyadh: tenants on their own subdomains hit one production deployment hosted in-Kingdom, where an edge resolver maps hostname to tenant_id, row-level security enforces data isolation in shared-schema PostgreSQL, and background workers reconcile Moyasar payments and Wafeq invoices against the tenant registry nightly. New tenants are provisioned in one transaction.
Approach

How the engagement ran

Delivery order matters as much as what was built. Each phase had to be safe to stop on.

  1. /001/

    Phase 1 — Audit and tenancy design

    Two weeks reading the three cloned codebases and mapping every divergence. Most differences were accidental drift; the few real per-customer customizations became configuration, not forks.

  2. /002/

    Phase 2 — Tenancy core with one live tenant

    Built the shared-schema core, row-level security, and subdomain resolution, then moved the client's own copy over first. Their team used the single-tenant version for a month before any partner migrated.

  3. /003/

    Phase 3 — Partner migration and provisioning

    Migrated the two partner firms with scripted data import, then built the self-serve onboarding transaction on top of the now-proven path, so the form does exactly what the first three migrations did by hand.

  4. /004/

    Phase 4 — Billing and financial close-loop

    Added Moyasar subscription collection, Wafeq invoicing, and the nightly reconciliation pass that treats the database as the registry of record for who owes what.

Outcomes

Results and key facts

Qualitative outcomes and engagement shape, without invented numbers.

One codebase, three companies, zero forks

The three diverging copies became one deployment. Every fix since ships to all customers at once, and the per-customer patch burden that consumed the client's week is gone.

Onboarding measured in hours

A new company goes from sales call to working subdomain in under a day, with the data import as the only slow step. The fourth customer took hours, not weeks.

Finance and the database agree

Nightly reconciliation has turned billing disputes into a lookup instead of an investigation: the tenant registry, Moyasar, and Wafeq report the same answer.

Engagement
Build, then ongoing support
Team
In-house Softwise engineers, no subcontractors
Stack
Next.js, Node.js, PostgreSQL (RLS), Moyasar, Wafeq
Tenancy model
Shared schema, row-level security, subdomain per tenant
Outcome
3 diverging codebases consolidated into 1, onboarding cut from weeks to under a day
FAQs

Questions about this multi-tenant saas project.

The questions this project is usually asked about. Still stuck? Ask us directly.

One deployed application serving many customers (tenants) whose data is isolated from each other. The alternative, one copy of the app per customer, multiplies hosting, deployment, and bug-fixing effort by the number of customers.

Shared schema with row-level security. At this client's scale it costs less to operate, migrates in one step, and the database enforces isolation. Database-per-tenant is worth its overhead when tenants demand physical separation, which none did.

Build time for a multi-tenant SaaS depends on the product surface far more than the tenancy layer. The tenancy design itself is a small share of the build. Most of the time goes into the product features, as it should.

The client operates in Saudi Arabia. Moyasar is the local payment gateway with mada and Apple Pay support their customers expect, and Wafeq issues ZATCA-compliant e-invoices. The architecture keeps payment and invoicing behind interfaces, so a tenant in another market could use a different provider without touching the tenancy core.

A tenant-specific feature request becomes a question about configuration first. Most requests — branding, seat limits, workflow defaults — are already per-tenant settings. A genuinely custom feature is scoped against the roadmap, because one tenant's fork is how this client ended up with three codebases in the first place.