evo.platform

The part every SaaS rebuilds, built once.

Tenancy, authentication, RBAC, billing, email and audit logging as a service the products call — not a library each one forks. Three products run on it today. The source is public under MIT.

The problem it solves

Every multi-tenant product needs the same unglamorous half: workspaces, sign-up, verified email, password rules, sessions, refresh tokens, roles, per-app access, subscriptions, dunning, invitations, an audit trail. None of it is what the product is for, and all of it has to be right before the product can be sold.

The usual outcome is that it gets rebuilt per app, slightly differently each time — which means a fix to a token bug lands in one product and not the other two, and nobody can say which. evo.platform exists so there is one implementation, and the products are tenants of it.

It is deliberately not a framework you inherit from. An app talks to it over HTTP through an SDK, keeps its own database and its own business logic, and can be developed with the platform switched off entirely.

What it is

  • A platform service — NestJS, Prisma and PostgreSQL, with an admin console for tenants, users, per-app access, revenue, audit, SMTP and the email copy itself.
  • Two SDKs — Node and Python, so an app in either language talks to the same API without hand-rolling token handling.
  • A Next.js starter template that runs standalone by default: a local users table, one implicit tenant, no external dependency. Set one environment variable and the same code is fully multi-tenant.
  • A scaffold CLIevo new <app> produces a tenant-aware application.

Flow 1 — signing up

Self-service sign-up arrives through an app, never at the platform directly. One request creates the workspace, its founding admin, and trial access to exactly the app the person came through — and nobody can sign in until the mailbox is proven.

Sign-up sequence: the person submits the app's form, the app's server makes one call to the platform, the platform creates the workspace, the founding admin and a trial access row, then emails a verification link; login is refused until that link is clicked.
Sign-up. One call creates the workspace, its admin and trial access to one app — and the only thing the platform initiates is the verification email.
  • One call does it. POST /auth/signup carries the app's clientId. The password is checked against the NIST/OWASP policy here, at the moment it is set — never at login, so tightening the rule cannot lock out an existing account.
  • Trial access is scoped to the app you arrived through. Every other app stays denied by default rather than enabled by oversight.
  • The mailbox is proven before anything works. Until the link is clicked, login is refused with Email not verified.
  • The platform initiates exactly one thing in this whole flow — the email. It never calls the app, and it never calls the browser.

Flow 2 — a tenant site, request by request

The everyday path: browser, edge, app, with login delegated to the platform and everything after it verified locally. The blue steps are the design's point.

Tenant site sequence: the browser reaches the edge proxy, the edge reaches the app over the internal network, the app delegates login to the platform, then verifies every later request locally against cached public keys while business data stays in the app's own database.
A request through a tenant site. After login the app verifies tokens locally, so it keeps serving even if the platform is down.
  • Four gates, in order — workspace status, credentials, verified mailbox, then per-app enablement. No enablement row means refused; there is no implicit yes.
  • Pull, not push. The app fetches the platform's public keys once and caches them, then verifies every later request by signature, locally. After login, the app does not need the platform to be up.
  • Tenant data never leaves the app. The platform holds identity and billing; business data lives in the app's own database, always tenant-scoped.
  • Billing changes bite at a boundary. A Stripe webhook flips the enablement row, and it takes effect at the next login or token refresh — not in the middle of somebody's request.

Flow 3 — an app asking evo-ai

Ask AI as EvoCivilCode runs it. evo-ai is a separate service; this is how an app built on the platform consumes it through the SDK's AskAi client, which is optional.

Ask AI sequence: the app's server proxies the question to evo-ai with a service key and an asserted tenant, retrieval runs inside that tenant's own collection, and a relevance gate can refuse before any model is invoked.
An app asking evo-ai. The tenant comes from the session, retrieval is scoped by construction, and the gate can refuse before a model is paid for.
  • The tenant comes from the session, not the request. A caller cannot name somebody else's workspace.
  • Isolation is by construction — a search runs inside the tenant's own collection, filtered by tenant_id, rather than being filtered after the fact.
  • The relevance gate refuses before a model is paid for. A refusal costs nothing and cannot hallucinate.
  • Refusals come back as designed states, flagged gated or unconfigured, so the app renders them deliberately instead of showing an error.

Four decisions that shaped it

  • Standalone by default. Every template runs with no platform at all — local users, one implicit tenant, zero external dependencies. You do not have to adopt the platform to use the template, which means the template gets tried on its own merits.
  • The platform never calls an app. Every arrow points at the platform, except the one email it sends. That is what lets an app keep serving verified requests while the platform is restarting.
  • Identity and billing here; business data there. The split is the whole reason a product can be sold without the platform becoming a single point of data loss.
  • Offline-first in the browser. The Next.js template ships an IndexedDB mutation queue with last-write-wins sync, so a tenant app survives a bad connection rather than blocking on one.

In production

It is not a demo. Three products run on it as tenants today — evo.provensheet, evo.civilcode and evo.docketmail — which is also what keeps it honest: a breaking change to the platform is a breaking change to three live applications, so the API is treated as a published contract rather than an internal detail.

The source is public and MIT licensed, including the admin console, both SDKs, the starter template and the scaffold CLI: github.com/evomedia-net/evo.platform.

Technology stack

  • Service — NestJS, Node.js, Prisma, PostgreSQL with row-level tenant isolation
  • Identity — JWT signed RS256 with rotating refresh tokens, JWKS for public-key distribution, WebAuthn passkeys
  • Billing — Stripe subscriptions and webhooks
  • Clients — Node and Python SDKs, a Next.js starter template, and the evo new scaffold CLI
  • Delivery — Docker, with the admin console shipped alongside the service

Back to Projects · Contact