Chazy: Designing a Future-Proof SaaS Architecture
Chazy is an AI-powered assistant that automates the collection of financial documents. The core concept sounds simple: match a transaction in the accounting system with an email attachment. However, in fintech, "simple" concepts often hide massive complexity regarding data isolation, audit trails, and state management.
The founders needed a working MVP to validate the market. However, my engineering priority was to ensure that this MVP wasn't just a throwaway prototype, but a solid foundation. I aimed for an architecture capable of evolving from a simple web tool into a multi-interface ecosystem without technical debt dragging it down.
🎯 The Challenge: Beyond the "Happy Path"
What the client described as a simple "match" operation is actually a distributed saga involving:
- Stateful Workflows: A "chase" isn't an instant event. It’s a lifecycle (Request → Reminders → Validation → Approval) that can span weeks.
- Strict Isolation: Handling financial data requires rigid multi-tenancy and audit logs from Day 1.
- Omni-Channel Vision: The roadmap demanded seamless access via a Web Dashboard, Email client, and Mobile web simultaneously.
🏗 Architecture: The Pragmatic Monolith
I resisted the urge to overengineer with microservices too early. Instead, I designed a Modular Monolith flanked by specialized helper services. This approach provided the operational simplicity of a single codebase, with the scalability of the cloud where it counted.
- The Core (The Brain): I enforced Domain-Driven Design (DDD) within a single, cohesive Node.js application. This ensures strict consistency for financial transactions and simplifies testing.
- The Satellites (The Muscle): Tasks that threatened the core's performance were extracted. Email ingestion happens at the Edge (to handle traffic spikes), while heavy media processing (PDF/Image slicing) runs on isolated serverless functions.
flowchart TB
subgraph Users["📡 Omni-Channel Access"]
Dashboard(["Web Dashboard"])
Extension(["Chrome Extension"])
EmailIn(["📧 Email Client"])
end
subgraph External["🌍 External World (via Provider Registry)"]
AI["AI Models<br>OpenAI / Anthropic"]
Acc["Accounting APIs<br>Xero / QBO"]
EmailOut["Email Service"]
end
subgraph Cloud["☁️ Chazy Infrastructure"]
Edge[/"⚡ Edge Function<br>(Ingests Emails, Absorbs Spikes)"/]
Core{"🧠 The Core<br>Node.js Monolith (DDD)<br>Business Logic & State"}
DB[("🗄️ PostgreSQL<br>Audit & Data")]
Worker["🏗️ Serverless Worker<br>(PDF Slicing & OCR)<br>*Heavy Lifting*"]
end
Dashboard -- API REST --> Core
Extension -- API REST --> Core
EmailIn -. Inbound Stream .-> Edge
Edge -- Sanitized Data --> Core
Core <-- Read/Write --> DB
Core -- Async Job --> Worker
Worker -- Result --> Core
Core <-- Interface Adapter --> AI & Acc
Core -- Interface Adapter --> EmailOut
Edge:::satellite
DB:::database
Worker:::satellite
classDef core fill:#f9f,stroke:#333,stroke-width:4px,color:black
classDef satellite fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:black
classDef external fill:#f5f5f5,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 5 5,color:#616161
classDef database fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,color:black
style Cloud fill:transparent
style External stroke:#BBDEFB
linkStyle 7 stroke:#FFF9C4,fill:none
🧬 Evolution: Surviving Complexity
The database schema expanded from ~8 core entities to over 30 in just 9 months. While such growth often turns startup codebases into "spaghetti," this project remained clean due to two strategic patterns embedded from the start:
1. The Provider Registry (Business Agility)
Hardcoding external integrations is a trap. I implemented a Provider Registry, where the core interacts only with abstract interfaces.
- The Result: When the business required adding a new accounting platform (switching from Xero to QuickBooks), it became a strictly additive task, posing zero risk to the stable core logic.
2. Global Decoupling (Tech Independence)
Every infrastructure tool—whether it’s a database, an email service, or an AI model—is treated as a replaceable plugin via Dependency Injection.
- The Result: When the project needed to upgrade from a basic Regex parser to an LLM, and later switch LLM providers, the switch required changing only a single configuration line. The complex business flows didn't even notice the change.
flowchart TB
subgraph CoreSystem["🛡️ Stable Domain Core"]
direction TB
Orchestrator("Document Processing Saga")
end
subgraph AdapterLayer["🔌 Integration Boundary"]
IParser{"IParser Interface"}
end
subgraph Providers["🧩 External Implementations"]
Regex["Legacy: Regex Parser"]
OpenAI["Current: OpenAI GPT-4"]
Anthropic["Future: Anthropic Claude"]
end
Orchestrator -- Depends only on --> IParser
Config>"⚙️ Config / Env Var"] -.-> IParser
IParser -. ❌ Disabled .-> Regex
IParser == ✅ Active Injection ==> OpenAI
IParser -. 🔜 Ready to Swap .-> Anthropic
Orchestrator:::core
IParser:::interface
Regex:::inactive
OpenAI:::active
Anthropic:::future
classDef core fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1
classDef interface fill:#fff3e0,stroke:#ff9800,stroke-width:2px,stroke-dasharray: 5 5
classDef inactive fill:#f5f5f5,stroke:#bdbdbd,stroke-width:1px,color:#9e9e9e,stroke-dasharray: 2 2
classDef active fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,color:#1b5e20
classDef future fill:#f3e5f5,stroke:#9c27b0,stroke-width:1px,stroke-dasharray: 2 2
style IParser color:#000000
🚀 Business Impact
The architecture proved its value not just in clean code, but in ROI. It allowed for rapid execution with the stability of a much larger organization:
- Internal Ecosystem: The system was so robust that it enabled the founders to evolve Chazy into a service provider for their primary business. The email ingestion and OCR engines were exposed as internal APIs, replacing a costly third-party vendor.
- Rapid Expansion: When the roadmap called for a Chrome Extension, I was able to reuse 100% of the backend logic. This turned a potential few-months sub-project into a 3-week sprint.
- B2B Readiness: The system is now being sold as a "Headless" API solution to partners—a pivot made possible solely because the core logic was strictly decoupled from the UI.