Mitigately
A greenfield debt restructuring platform connecting users with creditors through a 16–20 step intake flow, dynamic contract generation, and a legacy CRM integration that dated to 2008.
8 months · Greenfield build · Small team
At a Glance
Users onboarded
2,400+
through full 16–20 step flow
Completion rate
~74%
up from ~41% before resume feature
API latency
−65%
after async queue for CRM sync
Timeline
8 mo
design to production
* User and completion figures are approximate. Latency improvement reflects p95 API response time after queue introduction.
The Problem
Mitigately needed to guide financially distressed users through a 16–20 step intake process — sometimes across days or weeks — and then automatically generate a legal contract, route it for e-signature, and sync everything into a legacy CRM system last meaningfully updated in 2008.
The CRM was non-negotiable: it was the authoritative source of creditor data and identity. But its APIs were partially undocumented, returned HTTP 200 for both success and failure states, and had unpredictable response structures depending on input context. Designing around it — without hiding it — became the central engineering challenge.
Constraints
Architecture Overview
Frontend
Step-based state machine
Backend
Orchestration + validation layer
Persistence
Dual-write: MongoDB + CRM sync
Auth
Twilio OTP
Contract
Dynamic generation + Anvil e-sign
Post-onboarding
User dashboard + loan tracking
Key Engineering Decisions
Treating onboarding as a state machine, not a form flow
A traditional multi-step form would have collapsed under 16–20 steps with shared state, resumability, and per-step validation. I implemented a centralized step engine using React Context + Formik, where each step is an isolated validation unit within a global workflow state — index-driven, schema-validated, and type-safe throughout.
Enabled resume, skip-ahead, and validation without coupling between stepsDual-write architecture for CRM reliability
The CRM's most fundamental API — "get user by phone number" — always returned HTTP 200 regardless of whether a user existed. Standard error handling was useless. Rather than working around individual broken endpoints, I redesigned the data layer: MongoDB became the primary operational store, and the CRM was treated as a downstream sync target. An inverted lookup (query Mongo → extract CRM ID → fetch CRM record by ID) made responses deterministic.
Eliminated unreliable CRM query responses as a failure pointMoving CRM sync off the request path
Initially, CRM writes were synchronous and blocking. Every onboarding step that touched the CRM added latency and a potential failure point to what should have been a fast user-facing response. I introduced an internal async queue: MongoDB writes remained immediate, while CRM sync executed in the background. This also meant CRM failures were isolated — they didn't take down user-facing operations.
~65% reduction in p95 API response time; CRM failures no longer surface to usersPersisting step state to the backend for resumability
Users in a debt restructuring flow may need days between sessions. Storing state only in the browser would lose it. Step progress and partial form data were persisted to the backend on each transition, so on re-entry the system could reconstruct the last completed step, partial data, and navigation state exactly.
Completion rate improved from ~41% to ~74% after this was introducedOutcomes
- Built a complete 16–20 step onboarding engine with persistent, resumable state from scratch in 8 months
- Designed a stable integration layer around an unpredictable legacy CRM through dual-write + inverted lookup
- Reduced API latency by ~65% by moving CRM sync off the synchronous request path
- Improved onboarding completion from ~41% to ~74% with backend-persisted resumability
- Delivered full contract generation + e-signature flow via Anvil with OTP-secured access
- Shipped a post-onboarding dashboard with live loan status and installment tracking
Takeaway
“In real-world systems, reliability isn't achieved by waiting for perfect APIs. It's built by designing around imperfect external systems — through abstraction, persistence, and controlled synchronization. The CRM never got better. The system got resilient enough that it didn't matter.”