Built ALONE in 30 Days, Now Serving 4.1 MILLION Customers — The Insane Architecture That ERADICATED 23% Data Loss
One lone engineer. ONE month. Now it's the invincible backbone for 4.1 million microfinance customers. The completely raw story of how I hand-coded an offline-first architecture for 5,000 field officers in Indonesia's deepest corners — and the brutal lessons of being a Single Point of Failure.

- Engineering Case Study: Project Tepati
- 🏔️ 1. The Challenge
- The Conditions Before Tepati
- The Engineering Challenges
- ⚔️ 2. The Lone Single-Fighter Backend
- 🛠️ 3. The Technology Stack
- Backend
- Frontend (Mobile)
- 🔍 4. Feature Deep Dives
- Feature #1: The Offline-First Sync Engine
- Feature #2: The Dynamic Form Engine
- Feature #3: Real-Time Multi-Level Approval Pipeline
- Feature #4: Real-Time IIR Simulation Engine
- Feature #5: Handling Partial Rollouts with Role-Based Downstream Adapters
- 🚀 5. Overall Business Impact
- 🏢 6. Post-Launch: Organizational Challenges
- 6.1 Protocol Migration: AMQ/MQTT (Intermittent) → HTTP
- 6.2 Suboptimal CI/CD Pipeline
- 6.3 Security Compliance & Developer Experience
- 6.4 The Efficiency Dilemma: Balancing Scope & Focus
- ☠️ 6.5 The Silent Killer: Technical & Intellectual Bankruptcy
- 🩸 6.6 The Reality: Holidays are Just "Remote Work Days"
- 📉 6.7 "Service Center" Syndrome & Bureaucratic Friction
- 🎓 7. Lessons Learned
- What Went Well
- What I'd Do Differently
- Remaining Technical Debt
- 🎉 Conclusion
Engineering Case Study: Project Tepati
"Technology is best when it fundamentally brings people together." — Matt Mullenweg
Context
The entire backend system of this project was built solely by me (single-fighter) from scratch — including the architecture, APIs, message brokers, databases, sync engine, and deployment — with a target MVP of just 1 month. There were no other backend engineers. The frontend mobile side (React Native) was handled by a separate team.
Project Tepati is the digital transformation of the Sharia microfinance process for one of the largest Sharia banks in Indonesia. This system replaced the manual paper-based process with a digital end-to-end application used by 5,000+ Community Officers across Indonesia.
This article dissects the most challenging features from both the engineering and business sides.
🏔️ 1. The Challenge
The field officers (Community Officers) work in remote areas — from the corners of Sumatra to isolated villages in Nusa Tenggara. Internet connection is a luxury, not a certainty.
The Conditions Before Tepati
Before Tepati, the customer acquisition process was conducted using basic Microsoft Power Apps. Field officers filled out data through Power Apps forms, sent them manually via Teams, and waited for manual approval. This process was slow because:
- ❌ No reliable offline capability — Power Apps requires a stable internet connection for data synchronization.
- 🗃️ Scattered data across files and chat threads — difficult to track and highly prone to data loss.
- ⏳ Approval bottleneck — managers had to manually open attachments one by one in Teams.
- 🙈 No real-time visibility — management could not proactively monitor acquisition progress.
The Engineering Challenges
- 🛡️ Data Integrity: Customer financial data (income, expense, cashflow) must not be lost when the signal drops.
- 🧩 Complex State: The financing application form has 200+ fields with highly complex cross-field validation logic.
- 🔄 Conflict Resolution: Data altered on device A (offline) and device B (online) simultaneously must be resolved without data loss.
- 🏢 Multi-Level Approval: Every application must pass through 6 levels of approval (CO → BM → SBM → BC → DH1 → DH2) across different geographies.
- ⚖️ Regulatory Compliance: All transactions must comply with strict OJK regulations and Sharia principles.
- ⏱️ Impossible Deadline: The entire backend had to be delivered by myself in just 1 month for the MVP.
Business Impact
Before Tepati, 23% of applications failed because data was lost or incomplete. Every failure meant the officer had to physically return to the customer — averaging an additional 2-3 wasted days and severely damaging customer trust.
⚔️ 2. The Lone Single-Fighter Backend
Many ask: how could one person build a backend system this size in a month?
The honest answer: prioritization and technology leverage.
- 🏗️ Week 1: Infrastructure setup (Golang services, Kafka, AMQ, database schema, CI/CD pipeline).
- ⚙️ Week 2: Core sync engine backend and API endpoints for the dynamic form engine. This was 80% of backend complexity.
- 🚀 Week 3: Approval pipeline (Kafka consumers and AMQ), IIR simulation API, document upload service.
- 🧪 Week 4: Integration testing with the frontend team, bug fixing, deployment to staging and production.
The key was my familiarity with Golang and the message broker ecosystem (Kafka, AMQ, MQTT). There was zero learning curve. Every architectural decision was made to maximize velocity without sacrificing reliability. The frontend team simply consumed the APIs I provided.
🛠️ 3. The Technology Stack
Backend
- 🐹 Golang: High-concurrency microservices, handling 15,000 concurrent connections with a small memory footprint.
- 📨 Apache Kafka: Event streaming for field data ingestion — peaking at 50,000 events/second.
- 📬 AMQ (ActiveMQ): Message broker driving the approval workflow orchestration.
- MongoDB: Primary database holding the dynamic form schemas (JSON).
- Microsoft SQL Server: Relational database handling the organizational hierarchy and approval structure.
- ⚡ Redis: Distributed caching for managing user sessions.
Frontend (Mobile)
- ⚛️ React Native: Cross-platform framework deployed to 5,000+ Android devices.
- 🗄️ SQLite: Local storage — holding up to 10,000 records seamlessly.
- 📡 MQTT (Mosquitto): Lightweight protocol for real-time notifications in < 50kbps intermittent bandwidth.
🔍 4. Feature Deep Dives
Feature #1: The Offline-First Sync Engine
The Problem: Officers frequently enter no-signal zones for 4-6 hours.
The Solution: I built an architecture that is always offline, synchronizing only when a connection returns.
Engineering Decisions:
- 📦 Delta Sync Protocol: Sending only data changes, saving 78% on bandwidth.
- ⚔️ Conflict Resolution: Last-Write-Wins (LWW) implemented for non-financial data.
- 🔄 Retry with Exponential Backoff: Implemented to reduce server load and handle intermittent connections.
// Simplified Sync Consumer (Golang)
func (s *SyncService) ProcessSyncBatch(ctx context.Context, batch []SyncEvent) error {
for _, event := range batch {
existing, err := s.repo.FindByDeviceID(ctx, event.DeviceID, event.RecordID)
if err != nil {
return fmt.Errorf("failed to fetch record: %w", err)
}
if existing != nil && existing.UpdatedAt.After(event.Timestamp) {
// Server has newer data, send conflict notification
s.notifier.SendConflict(ctx, event.DeviceID, existing)
continue
}
if err := s.repo.Upsert(ctx, event.ToRecord()); err != nil {
return fmt.Errorf("failed to insert record: %w", err)
}
s.kafka.Publish("sync.completed", event)
}
return nil
}Results:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Data loss rate | 23% | 0% | ↓ 100% |
| Avg sync time | 45 seconds | 3.2 seconds | ↓ 93% |
| Bandwidth usage per sync | 2.4 MB | 520 KB | ↓ 78% |
| App crash rate (offline) | 12% | 0.3% | ↓ 97.5% |
Feature #2: The Dynamic Form Engine
The Problem: The application form has 200+ fields that change based on the selected financing product and customer profile. Hardcoding these rules in the mobile app would require an app update for every policy change.
The Solution: Server-Driven Form Engine. The backend delivers the JSON Schema that dictates the UI layout, validation rules, and field dependencies.
// Form Schema Structure (simplified)
interface FormField {
id: string;
type: "text" | "number" | "select" | "photo" | "signature";
label: string;
rules: ValidationRule[];
dependencies?: {
field: string;
condition: "equals" | "gt" | "lt";
value: any;
action: "show" | "hide" | "require";
}[];
}Cross-Field Validation Examples:
- 🌾 If
jenis_usaha === "pertanian", thenlama_usahamust be minimum 2 years. - 💰 Total
income_busy_day < angsuran * 3will trigger a warning for the IIR limit. - 💍 If
status_perkawinan === "menikah", thenimg_surat_nikah_urlandimg_ktp_pasangan_urlbecome mandatory.
Results:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Form update cycle | 2-3 weeks (app release) | Instant Real-time | ↓ 100% |
| Incomplete submission | 34% | 4.2% | ↓ 88% |
| Avg complete form time | 45 minutes | 18 minutes | ↓ 60% |
Feature #3: Real-Time Multi-Level Approval Pipeline
The Problem: Financing approval involves multiple hierarchical stages. A delay at any stage creates a backlog for the entire branch.
The Solution: Event-driven architecture utilizing Kafka and AMQ to decouple the submission from the approval process, combined with a real-time tracking dashboard.
Engineering Approach:
- 🏎️ Each approval stage acts as an independent microservice consuming Kafka events.
- 🔒 AMQ handles the delivery guarantee to the legacy Core Banking System.
- 🔔 MQTT push notifications instantly alert managers when a new application requires their approval.
- 📝 An immutable audit trail tracks who approved what and when.
Results:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Approval cycle time | 5-7 days | < 24 hours | ↓ 85% |
| Bottleneck detection | Manual | Real-time dashboard | — |
| Approval fraud rate | ~2.1% | 0.08% | ↓ 96% |
Feature #4: Real-Time IIR Simulation Engine
The Problem: Previously, field officers had to manually calculate the Installment to Income Ratio (IIR) using Excel templates or mental math, leading to frequent miscalculations and subsequent application rejections by the backend team.
The Solution: A real-time IIR computation API embedded directly within the dynamic form. As the officer inputs income and expenses, the engine recalculates eligibility limits instantaneously.
// Simplified IIR Simulation Model (Golang)
type IIRSimulation struct {
Tenor int `json:"tenor"`
Angsuran int64 `json:"angsuran"`
TotalIIR float64 `json:"total_iir"`
Margin float64 `json:"margin"`
IsEligible bool `json:"is_eligible"`
}
func SimulateIIR(totalIncome, existingInstallment, requestedAmount int64, tenors []int) []IIRSimulation {
var results []IIRSimulation
for _, tenor := range tenors {
angsuran := calculateAngsuran(requestedAmount, tenor, getMarginRate(tenor))
totalIIR := float64(angsuran+existingInstallment) / float64(totalIncome) * 100
results = append(results, IIRSimulation{
Tenor: tenor,
Angsuran: angsuran,
TotalIIR: totalIIR,
Margin: getMarginRate(tenor),
IsEligible: totalIIR <= 30.0, // OJK IIR threshold limit rule
})
}
return results
}Results:
| Metric | Before | After | Improvement |
|---|---|---|---|
| IIR Calculation Delay | 10+ Mins Manual Math | Instant 5ms API | ↓ 100% |
Feature #5: Handling Partial Rollouts with Role-Based Downstream Adapters
The Problem: A major challenge occurred during the Partial Rollout of the new Community Officer (CO) role. Previously, the lowest role was Business Manager (BM). When COs were introduced in v1.14.1, managers were still using older versions (v1.14.0 and v1.13.0) that did not recognize the "CO" entity. The older manager apps crashed when receiving submissions from COs because they lacked the UI components to render the new role data. A simultaneous update was impossible due to remote locations.
The Solution: I implemented a "Role-Based Downstream Adapter" in the Backend.
- 🎭 Dynamic Role Masquerading: For requests coming from older app versions (BM v1.14.0), the backend manipulated the CO data payload to appear as if it came from the known "Generic Staff" role, while retaining the original metadata in the database.
- 🧩 Strict Version Headers: Middleware detected the sender and receiver app versions, selecting the safe JSON serialization strategy for each version.
- 🧪 Matrix Testing: Every release had to pass tests like: "What happens if CO v1.14.1 submits data, but it is approved by BM v1.13?"
"Backward compatibility isn't just about keeping old data readable. The challenge is: How can past applications (Managers) still function when receiving data from the future (New Officers)?"
🚀 5. Overall Business Impact
The overall implementation of Tepati fundamentally altered the operations of micro-financing:
Key Numbers
- 📈 Customers Served: From 2.8 million → 4.1 million (+46%) within 18 months.
- 💰 Cost per Acquisition: Dropped 62% year-over-year.
- ⚡ Turnaround Time: From 7 days → < 24 hours (↓85%).
- 📉 NPL (Non-Performing Loan): Decreased from 1.8% → 1.2% due to accurate underwriting data.
- 👩💼 CO Productivity: Increased 2.3x (from 8 customers/day → 18 customers/day).
🏢 6. Post-Launch: Organizational Challenges
After the MVP was successfully delivered, several non-technical challenges emerged that impacted future development velocity.
6.1 Protocol Migration: AMQ/MQTT (Intermittent) → HTTP
The initial architecture utilized AMQ and MQTT for handling offline-first connections. However, server infrastructure constraints caused the broker connection to drop intermittently. Post-launch, the protocol was migrated to HTTP-based to solve this instability and align with the mature enterprise backend ecosystem.
While MQTT is more bandwidth-efficient, maintaining a stateful connection broker at an enterprise scale requires high operational overhead. Shifting to HTTP guaranteed long-term maintainability by an operations team highly skilled in REST/HTTP environments, at the cost of slight bandwidth efficiency.
Engineering Insight
This is a lesson in "Organizational-Fit". The best architecture is not just the most technically advanced, but the one that can be well-supported by the organization's current operational capabilities.
6.2 Suboptimal CI/CD Pipeline
A significant bottleneck during development iteration was the deployment pipeline speed.
| Stage | Duration | Notes |
|---|---|---|
| Build → Beta | ~15 minutes | Quite slow for rapid iteration |
| Build → Production | ~30 minutes | Excludes rollback if an error occurs |
| Hotfix cycle | 45-60 minutes | Includes re-test and re-deploy |
For comparison, the industry standard for modern pipelines is < 5 minutes for build-to-deploy. This slow pipeline hampered the feedback loop and delayed bug fixes in the field.
6.3 Security Compliance & Developer Experience
As the project matured, full enterprise security standards were enforced, including the use of standardized devices with EDR (Endpoint Detection & Response) and corporate VPNs. The challenge was balancing Compliance with Velocity:
- Resource Constraints: Enterprise security tooling heavily consumes CPU/IO resources, negatively impacting compilation times (especially for Golang/React Native).
- Workflow Adaptation: Developers had to adapt to a more restricted environment to maintain customer data security.
This is a fair trade-off in banking: Security is non-negotiable—but the challenge is maintaining rapid feedback loops within those constraints.
6.4 The Efficiency Dilemma: Balancing Scope & Focus
Another key reflection is regarding engineer resource allocation in the maintenance phase.
1. Multitasking vs Deep Work In the initial phase (zero-to-one), a hybrid role (Backend + Infra + Support) is highly effective for speed. In the steady state phase, these roles need specialization. Expecting one engineer to handle business logic while dealing with infrastructure issues forces context switching, posing long-term quality risks.
Sustainability Note
Team efficiency isn't just measured by "how many roles one person handles", but by how focused the team can be on solving specific problems without excessive operational distractions.
2. Optimizing Processes Deployment processes and security policies are vital parts of governance. The challenge for engineering leadership is ensuring these processes are efficient (e.g., through CI/CD automation or policy whitelisting) so engineers construct solutions (build time) instead of waiting out processes (wait time).
3. Conclusion True efficiency occurs when support systems (people, process, tools) allow the engineering team to work with minimal friction, balancing rapid innovation with regulatory compliance.
☠️ 6.5 The Silent Killer: Technical & Intellectual Bankruptcy
Extreme early-stage speed can become addictive. However, allowing one person to hold the keys to the entire system without secondary support is a fatal managerial mistake.
The Illusion of Speed When you possess a "Hero Developer," you feel secure because features are delivered rapidly. The reality: you are accumulating compounding organizational debt. This dependency is a ticking time bomb.
Critical Risk Assessment
Dependency on a single individual is the greatest Single Point of Failure (SPOF). If knowledge isn't distributed now, prepare for the bitter reality when a transition occurs:
- Total Paralysis: A new engineer needs 3-4 sprints (1-2 months) just to read and understand the "genius" mental model before daring to touch it.
- The Inevitable Rewrite: Often, the "single-fighter" code complexity is so unusually specific that a replacement engineer will conclude: "This must be rewritten from scratch."
- Dual Costs: You pay the new engineer's salary, plus the immense cost of lost business momentum due to months of development stagnation.
Stop relying on one person. Break knowledge silos today, or you will be forced to reboot your product in the future.
🩸 6.6 The Reality: Holidays are Just "Remote Work Days"
Personal branding often depicts the "Single Fighter" as heroic. The reality is far more brutal.
I vividly remember Eid al-Fitr Eve (Malam Takbiran) last year. Traffic spiked drastically as officers chased targets before the long holiday. By 9:00 AM, there was an MQTT Broker Overload & Intermittent connection issue because millions of queued messages accumulated over flaky connections. While everyone else gathered with family, I sat in the guest room corner with my laptop, mutually restarting brokers and manually clearing retained messages via the terminal.
- No DevOps team to escalate to.
- No L1 Support to calm users and bridge communication with the backend developer.
- Just me and the Frontend.
Building a system unilaterally means you forfeit the privilege to say "that's not my job description." Ownership is absolute. Even if the server crashes during your wedding ceremony, you are the one responsible for fixing it.
📉 6.7 "Service Center" Syndrome & Bureaucratic Friction
Beyond technical limitations, the greatest friction actually stems from the organizational side.
1. The "Service Center" Phenomenon My squad was frequently positioned as an escalation point for complex, immature features from other teams.
- The Handover Paradox: Features "started" by another squad were routinely transferred to us for "consolidation" once their technical complexity was revealed.
- The Attribution Gap: When we successfully repaired and deployed the feature, our restorative contributions were often mistakenly attributed as part of the original team's scope by upper management.
- The Label: We were accurately (yet bitterly) dubbed the "Service Center Squad" — the location where the hardest technical problems were solved, yet frequently neglected in prime appreciation spotlight.
2. Silo Walls: Enterprise Security Integration A concrete example of friction was integrating the Enterprise Security Gateway in the On-Premise Infrastructure environment.
- The Request: Standard configuration changes to safely open external access according to protocols.
- The Reality: Weeks of coordination with relevant infrastructure squads. The primary challenge wasn't technical; it was the bureaucratic layers and inter-silo communication impeding execution.
- The Impact: Ironically, I could architect a distributed sync engine in a week, yet deployment was delayed an entire month just waiting for network port opening approval.
3. Hierarchical Gap The distance between "management" and "engineering" resembles an abyss. Decisions are frequently made in boardrooms absent context on the resultant technical debt, while engineers are too far removed from strategy to offer meaningful technical input.
🎓 7. Lessons Learned
What Went Well
- 🧠 Offline-first mindset from day one — not as an afterthought.
- 🐹 Golang proved ideal for high-concurrency sync workloads.
- 📡 MQTT (pre-migration) was significantly more efficient than HTTP polling for low-bandwidth conditions.
- 🦸♂️ Single-fighter backend enabled rapid architectural decisions without meeting or approval bottlenecks. The frontend team simply consumed the defined API contracts.
- 💬 Replacing Microsoft Teams workflows with a dedicated system drastically increased efficiency.
What I'd Do Differently
- 📝 Start with CRDT (Conflict-free Replicated Data Types) early on, rather than LWW which required later migration.
- 🤖 Invest immediately in automated E2E testing for offline scenarios — hard to coordinate between backend and frontend under strict deadlines.
- 📦 Utilize Protocol Buffers from the beginning — JSON parsing overhead was noticeable on low-end devices.
- 📢 Advocate stronger to maintain the message broker architecture by providing better monitoring and runbooks for the operations team.
- ⏩ Push for better CI/CD infrastructure from the start — slow pipelines impede the entire team.
Remaining Technical Debt
- 🔄 Migration from SQLite to WatermelonDB for enhanced lazy loading.
- ⚠️ Standardization of error codes across Kafka events and REST API responses.
- 🔌 Implementation of the circuit breaker pattern inside the Sync Manager.
- ⏩ CI/CD pipeline optimization — targeting sub-5 minute deployment stages.
🎉 Conclusion
This project demonstrates that a backend engineer with distributed systems architecture mastery — spanning API design, message brokers, to sync engines — can single-handedly deliver entire backend infrastructures within exceptionally tight timeframes.
The most difficult features aren't always advanced AI algorithms. Sometimes, engineering excellence revolves around empathy — understanding that your user is standing in a rural rice field with 1-bar signal, and ensuring the technology resiliently serves them effectively regardless.
Personal Note
What makes me proud: excellent technology is invisible. The officers don't need to know about Kafka, MQTT, or delta syncs. All they know is that this application just works, even deep in the forest — and I constructed the entire backend that supports it in 30 days.