Systems · From July 2026
Architecture for a multi-tenant lead-intelligence SaaS
Wrote the requirements, architecture and detailed design for a multi-tenant lead SaaS, and built a demo for client review.
The problem
What was needed, and what made it hard.
A client wanted a SaaS that ingests public-records filings, enriches each case with owner contact data from third-party contact-data APIs, validates quality, and pushes sales-ready leads into each customer's CRM. Many customers share one deployment. Enrichment costs money per lookup, so each lookup had to be billable to the tenant that asked. The filings themselves are public and identical for everyone. The client's requirements arrived in rounds, so the design had to stay stable while three decisions stayed open. I owned the specification and the demo, so the two had to agree.
My role: Sole owner: architect, requirements owner, specification author and builder of the frontend demos.
The system
The specified path of one lead, in order. This is design: I built only the demo screens, not this pipeline.
Select a component to read what it does and how it fails.
- Filing ingest. Polls public-records sources once for everyone, which keeps it cheap. If polling stalls, every tenant's filings go stale together.
- Shared store. One database holds property, owner, filing and enrichment results once. A bad write here reaches every tenant.
- Enrichment. Contact-data lookups run only when a tenant asks, and are billed to that tenant. A provider outage delays requests, not ingestion.
- Tenant database. Everything a tenant owns lives in its own database. Isolation is structural, not a query filter; the price is many migrations.
- CRM delivery. Validated, sales-ready leads are pushed into the customer's CRM. As the last hop, its failures are what the customer sees first.
- Control plane. A separate repo, backend and privilege boundary for provisioning, tenant resolution and migrations. It reaches every tenant, so guard it.
Decisions
What I chose, what I turned down, and what it cost.
1Share the public data, isolate the paid data
- I chose
- One shared source-of-truth database holds property, owner, filing and enrichment results once. Everything a tenant owns sits in a database per tenant.
- I turned down
- Copying the public data into every tenant's database.
- Why
- Filings are identical for everyone, so wide, cheap ingestion should happen once. Enrichment is expensive and demand-scoped, so it stays billed to the tenant that asked.
- What it cost
- Two data planes to keep consistent, and a bad write to the shared store reaches every tenant.
2Split the control plane
- I chose
- A separate repo, backend and privilege boundary for the admin plane, with provisioning, tenant resolution, migration execution and shared-infrastructure placement written down.
- I turned down
- Building it beside the tenant backend as extra routes.
- Why
- The code that can create, migrate and inspect every tenant should not share a deploy or a credential set with the code every tenant's users reach.
- What it cost
- Two backends to run, and a written contract between them that has to stay true.
3Trace every requirement to a screen
- I chose
- Requirement IDs run from the requirements document into the demo, and the demo shows only what the document specifies.
- I turned down
- A richer demo with screens the document did not ask for.
- Why
- A reviewer can walk from any requirement to the screen that shows it, so disagreement becomes a change to the specification, not an argument about a mock-up.
- What it cost
- The demo could not reveal what the requirements missed. That took the client's written input, which left three decisions open for me to close.
What broke
The honest boundary.
Nothing broke, because nothing ran in production. The demo is a review artifact: it shows the specified screens, not a working pipeline. When I last checked, the production backend had not been started, so I have no incident to report and will not invent one.
Outcome
What came of it, and what it was built with.
- I finished the requirements (v1.3), architecture (v1.3) and detailed design (v1.1) documents, after a discovery pass on market, competitors, data sources, workflows and personas.
- I closed the three decisions the client's second-round input left open: a dual-mode data-pool toggle, dismissed-date handling and a filing schema placeholder.
- This took the architecture and demo from nothing to something a client could review. It did not take a product to launch.
Built with
- Next.js
- React
- Tailwind CSS
- Docker
- Traefik
Specified, not built by me
- NestJS
- Python
- FastAPI
- Celery
- PostgreSQL
- OpenSearch
- Redis
- Auth0
- Terraform
The rule I took from this
The line I keep from this project.
Write the specification first, and trace every requirement to a screen.
Rule 7 of 10
Keep reading
Next case study · Applied AI
HireTrack: an AI résumé tailor and job-application tracker
Built an open-source, offline-first tracker that moves applications through a fixed pipeline and tailors résumés with a deterministic-first LangGraph flow.
Also worth reading
Working on something similar?
Tell me what the system is doing, and what it should be doing.