Skip to content
Vertical AI

How We Rebuilt a 318,000-Line Car Rental CRM on Our Shared Platform in 4 Weeks

Rebuilt a 318,000-line production car rental CRM on the shared multi-tenant platform in 4 calendar weeks. 86% of the schema stayed rental-specific.


Mikhail Savchenko·June 1, 2026·8 min read
Vertical SaaSInite EcosystemCase StudyMonorepoMulti-Tenant

What this case study is about

We are building a family of SaaS products that share most of their plumbing. Login, billing, customer messaging across channels, audit log, internationalization, AI assistant runner, security primitives, rate limiting - all of that is generic. We wrote it once as a set of shared packages and we reuse it across products. Each vertical product (a car rental CRM, a real estate CRM, an e-commerce backend, an events ticketing system, a restaurant operations app, a sports club booking platform, and so on) only writes the part that is genuinely specific to its industry. The bet is that most of a SaaS product is substrate you can share, and a small but real domain-specific layer is what differentiates one vertical from another.

This case study is about the first time we proved that with a real codebase. We took our production car rental CRM - 318,000 lines of TypeScript, four years of accreted business logic, live operators running real rental businesses on it - and moved it onto the shared platform in four calendar weeks. Operators kept using the app the whole time. By the end of week 3 they were running their real daily reservations through the rebuilt version.

The point of writing this up is not the engineering work itself. The point is the answer to a real business question: when you try to share code across very different products, how much do you actually save, and where does the sharing break down?

What the migration actually looked like

Before the migration: a standalone car rental CRM. Years of code, its own login system, its own billing wrapper, its own customer-messaging integration with WhatsApp and Telegram, its own audit log, its own rate limiter. All of it written specifically for the rental product, none of it reusable.

After the migration: the same 318,000 lines of rental-specific business logic - vehicles, reservations, tariffs, inspections, fines, loyalty, fleet investor reporting - now sit on top of the shared platform. The login, billing, messaging, audit log, rate limiter, internationalization, AI assistant - all of that runs on shared packages we wrote once and reuse across products.

We measured the boundary precisely. Of the 1,872 lines of database schema, about 220 lines (12%) is the generic multi-tenant substrate - the user, company, role, API key tables; the customer-message inbox; the audit log. About 1,650 lines (88%) is the genuinely rental-specific domain - 67 different tables for vehicles, reservations, tariffs, inspections, and the rest.

That 12-vs-88 ratio is the answer to the business question. The 12% turns out to be enough boilerplate to make sharing worthwhile - because every one of our 15 planned vertical products would have written its own version of that 12% otherwise. Writing it once and reusing it across 15 products is the math that justifies the shared-platform investment. The 88% is the part that genuinely differs between rentals and real estate and restaurants, and trying to share it would have been a mistake.

Four weeks, week by week

WeekWhat got built
1Foundation. The rental project joined the shared workspace. The five mandatory multi-tenant database tables were aligned to the shared login package. Login itself was rewired through the shared package. By end of week, the validator confirmed the tenancy layer matched the platform specification.
2Shared services. Customer messaging across web, WhatsApp, and Telegram running through the shared inbox package. The AI assistant runner replacing the local LLM wrapper. The shared API layer replacing the local rate limiter and error reporter. Customer conversations flowing end-to-end through the platform by end of week.
3Operator surfaces. All 34 multi-tenant routes stabilized. Storefront preset, AEO surface for AI search visibility, KPI dashboard. Operators using the rebuilt app for real daily reservations from Wednesday onwards.
4Hardening. Payment adapters integrated. The API surface for external AI agents (ChatGPT, Claude, custom) live for rental operations. Personal data encrypted at rest. Audit log fully wired.

The bottleneck across all four weeks was not the engineering work. It was that the production rental data kept contradicting our assumptions about how the shared platform should be specified. Three times in week 2 we had to choose: rewrite the rental data model to match the platform specification, or rewrite the platform specification to match the rental data. Each time we rewrote the specification. The reason is principled - if the platform specification cannot survive contact with a real, four-year-old production SaaS product, then the specification is the thing that is wrong. The rental codebase is the empirical reference. A platform that does not carry it cleanly is not yet a platform.

What we learned about the platform

Three things changed because of this migration. One of them turned out to be the most important architectural decision in the entire platform.

The franchise problem - and the fifth multi-tenant table.

We had specified four mandatory multi-tenant tables: users, companies, roles connecting them, API keys. We thought permissions could be derived from the role assignment alone. The rental codebase had a franchise pattern that broke that assumption: a specific Manager at a specific rental branch needs investor-reporting access. The Manager role does not normally have it. The standard role table could not express that.

We added a fifth mandatory table for per-company permission overrides. Every conforming vertical product now inherits it. The trigger was rental-specific; the solution is generic.

Customer messaging - the richer model wins.

Our shared customer-messaging package started life with a simpler model that some of our other vertical pilots had used. Rentals needed richer semantics: multiple support agents working one channel at once, with proper assignment and handoff. We kept the richer model. The other pilots will migrate to it on their next cycle. The principle: the shared package has to be the strict superset of what its consumers need. If the rental pilot needs a feature, the shared package gets it, and every other pilot benefits.

The valuable accident - making AI agents safe to use.

Our platform specification has a section we called "obligations" - business invariants like "no vehicle handover until the contract is signed and the deposit is settled" or "no deposit released until the post-rental inspection is recorded". Originally we treated this as documentation. The runtime did not enforce it; the application code was supposed to.

During week 4 of the migration, we wired those obligations directly into the API layer that external AI agents call. The result: when ChatGPT or Claude calls our create reservation API on behalf of a customer, the platform automatically refuses to let the agent skip the contract or the deposit. The agent does not need to know the rule existed; the runtime returns a structured error pointing at exactly which business invariant got violated.

This is the part of the work that, in hindsight, mattered most. Letting an external AI agent drive your reservation API is supposed to be the scary thing about agentic SaaS - the agent will mess up the rules, skip the deposit, hand over keys without a contract, leave you with the legal exposure. We do not let it. The rules are enforced one layer below the agent, in code that the agent cannot bypass. Every vertical we ship from now on gets this same safety layer for free. We did not plan it that way; the rental migration forced us into it.

Where this sits in the wider plan

inite.rent is one of 15 vertical SaaS products in the Inite family. Two more migrations are running in parallel right now (real estate and e-commerce), and four more are in the queue (events ticketing, restaurants, travel, sports clubs). Each starts from a different legacy codebase but follows the same migration pattern that the rental case study established. The architecture that makes all 15 verticals share substrate without forking is in constitution vs runtime monorepo; the thesis behind cloning verticals at four-week cadence is in one engine, many skins.

This case study is also a worked example of our 6-stage transformation methodology applied to our own engineering work. Break audits the legacy code. Hold stabilizes the schema so the migration has consistent inputs. Track instruments the call patterns so the shared platform's behavior can be compared to the legacy. Cut removes the locally-duplicated horizontal code that the shared platform now provides. Cast ships the rebuild to operators using it daily. Form is the three months of boundary tuning afterwards that produced the platform shape we have today.

The methodology works the same way on engineering work as it does on B2B automation engagements with paying clients. That itself is a useful proof point. The methodology is not just a sales document; we run it on ourselves.

In one sentence

The 318,000-line car rental CRM rebuild proved that our shared multi-tenant platform can carry a real, four-year-old, production-load SaaS product; the migration calibrated which parts of the platform we had right, which we had wrong, and produced an accidental safety layer for agentic API calls that we now expect to be the single most valuable piece of the platform over the next two years.

The car rentals are real. The 318,000 lines are real. The migration is the reference for what comes next.

Frequently Asked Questions
  • 01How is this different from a normal pnpm workspace or Nx monorepo?+

    A workspace gives you code sharing. We needed a contract on top of code sharing. The shared packages declare what every consuming vertical must implement (five tenancy tables, the inbox model, the API wrapper shape). A validator runs in CI and fails the build if a vertical drifts from the contract. Without that, a 15-product family quietly forks the shared packages within six months, and you end up with 15 maintenance burdens instead of one. The contract is what stops the drift.

  • 02What happens when a vertical needs something the shared platform does not have yet?+

    Two paths. If the need is clearly generic (something multiple verticals would want), it goes into the shared platform on the next release and the vertical waits. If the need is genuinely industry-specific, it stays in that vertical's own code. The judgement call is made against the shared spec, not by whoever asks loudest. The rental pilot itself produced three additions to the shared platform; future verticals will produce others. The platform grows by demand from production use, not from speculation.

  • 03How do you handle breaking changes to the shared platform without breaking 15 products?+

    Semantic versioning on every shared package, plus a contract validator that compares each vertical's declared dependency version against what it actually uses. Breaking changes ship in a major version that requires explicit migration; verticals on the old major keep working until they migrate. The validator catches drift in CI. The hardest part is not the tooling - it is the discipline to refuse one-off patches that would help one vertical but skew the contract for the other 14.

  • 04Why not just use Stripe, Auth0, Twilio for the substrate instead of building it?+

    We do use external providers for the parts where they are clearly best - Stripe for card processing, Twilio for SMS, third-party LLMs for the assistant runner. The shared platform is the layer above them: the multi-tenant data model that maps a Stripe customer to one of our companies, the permission graph that decides who can see which inbox, the audit log that survives across providers. External SaaS solves single-vendor problems; the platform solves the cross-cutting concerns that no single vendor owns. Both layers are real and both belong.

  • 05Can other companies use this shared platform, or is it Inite-only?+

    Inite-only for now. The platform is calibrated against the specific shape of SaaS products we build - multi-tenant B2B with AI assistant integration, agentic API surfaces, our specific safety-rule model. A general-purpose shared SaaS platform would need different tradeoffs (looser contracts, more configuration surface, slower release cycles) and we are not building that. The interesting reusable artifact from this work is not the code but the methodology - the 6-stage transformation protocol and the contract-then-share pattern. Both of those are documented and applicable outside the Inite family.

Keep reading