Every few months a client comes to us with the same story: they pay per-seat for a big-name CRM, use maybe 20% of it, fight the other 80%, and still end up exporting to spreadsheets to get the report they actually wanted. At some point the "off-the-shelf" tool becomes the bottleneck instead of the accelerator.
We've built enough business software now to have a strong opinion about why this keeps happening — and what a saner architecture looks like. This isn't a "build everything from scratch" post. Rebuilding Salesforce is a terrible idea. But there's a large, underserved middle ground between forcing your process into someone else's product and writing a CRM from zero, and that's where a modular approach earns its keep.
The real cost of generic SaaS isn't the subscription
The monthly bill is the visible cost. The hidden costs are worse:
Process distortion. Your team changes how they work to fit the tool's assumptions. Multiply that across 40 people and you're paying for friction every single day.
Integration tax. The moment you need the CRM to talk to your billing system, your WhatsApp flows, or your internal inventory DB, you're either paying for premium connectors or writing brittle glue code around an API you don't control.
Data gravity. Your most valuable asset — customer and pipeline data — lives in a schema you can't see and can't query directly. Every custom report becomes a support ticket.
Per-seat scaling. Costs grow linearly with headcount, not with value. Adding a warehouse team of 15 who each need to update one field still costs 15 seats.
None of this shows up in a sales demo. All of it shows up in month six.
Modules over monoliths (and over microservice sprawl)
The mistake people make when they hear "custom" is imagining a giant bespoke monolith that only one contractor understands. That's just a different prison.
The approach that's held up best for us is a modular platform: a small set of well-defined, independently deployable modules that share a common data and auth layer. Think of the units a business actually reasons about — Leads, Customers, Opportunities, Invoices, Tasks, Messaging (SMS/WhatsApp/Email), Approvals, Analytics — and treat each as its own module with a clean contract.
A few principles that made this work rather than turning into distributed-systems pain:
Split by business capability, not by technical layer. A "Lead" module owns its data, its rules, and its API surface. It does not reach into another module's tables. When you split by capability, a new requirement ("add a follow-up SLA to leads") touches one module, not seven.
Don't reach for microservices until a module actually needs independent scaling. Start each capability as a module inside a modular monolith with strong internal boundaries. Only extract a service when it has a genuinely different scaling or availability profile — for example, a messaging/webhook consumer that gets hammered by WhatsApp callbacks while the rest of the app idles. Premature service extraction buys you network calls, distributed transactions, and observability overhead you didn't need yet.
One shared identity and permission layer. Role-based access, activity logging, and authentication should live once, at the platform level, and be enforced consistently across every module. Re-implementing auth per feature is how security holes are born.
Own your schema. The single biggest win of going custom is that customer data lives in a database you can query. Real-time dashboards stop being a paid add-on and become a SELECT. When the CEO asks "what's our lead-to-close time by region this quarter," it's a query, not a quarter-long integration project.
The stack that keeps this cheap to run
The other quiet advantage: build on open-source and the per-user licensing math disappears. Our default toolkit is boring on purpose — Next.js and React on the front end, Node.js with TypeScript on the back end, a relational database as the source of truth, and a microservices/modular split only where load demands it. Nothing exotic. The goal is that any competent engineer can read the codebase in a week, not reverse-engineer a black box.
TypeScript across the boundary matters more than it sounds. When your module contracts are typed end to end, "rename a field on the Opportunity module" becomes a compile error in every consumer instead of a runtime surprise in production three weeks later.
When you should not do this
Honesty check, because this isn't free:
If a standard SaaS genuinely fits 90%+ of your process, buy it. Custom software you don't need is its own kind of waste.
If you have no one to own the software long-term, a bespoke system will rot. Someone has to maintain it.
If your requirements are still changing weekly, stabilize the process first, then encode it. Modeling a moving target in code is expensive.
The sweet spot is a business that has outgrown a template tool, has real integration needs, and treats its operational data as a competitive asset. That's exactly the profile where the modular approach pays for itself.
Takeaway
The choice was never "SaaS vs. build it all." It's about owning the parts that are core to how your business actually runs, integrating cleanly with everything else, and keeping the architecture modular enough that change is cheap. Buy the commodity, build the differentiator, and make sure your data lives somewhere you can actually reach it.
Top comments (0)