Failing Software Project Rescue: Shipping a Stalled Mobile App in 90 Days
When a mid-sized Vietnamese retail chain approached us in early 2024, their internal metrics told a story we have seen too many times. Their customer loyalty app, originally scoped as a six-month build, was fourteen months in, roughly 180 percent over budget, and still not ready for their 40,000 active members. Two vendors had already come and gone. A third quote had just landed on the CEO's desk, promising another nine months of work. Instead, they called us for a two-week audit. Ninety days later, the app was live in the Apple App Store and Google Play, running the year-end loyalty campaign that unlocked over 12,000 new signups in its first month.
This is the story of how we got there, what actually went wrong, and what a failing software project rescue looks like when you strip away the vendor politics.
Project overview
The client is a specialty retail brand operating 34 stores across three provinces, with a loyalty program that had grown too complex for its original SMS-and-plastic-card infrastructure. The vision was straightforward: a native mobile app for iOS and Android that let members check point balances, redeem rewards at the point of sale, receive personalized offers, and book in-store services. On paper, a well-understood build with mature libraries and clear business logic.
The original delivery plan called for a six-month engagement with a Ho Chi Minh City studio, followed by a three-month handover to an internal team. Fourteen months in, the internal team had not been hired, the second vendor had rebuilt the backend twice, and the app itself still crashed on Android 12 within two minutes of the loyalty scan flow. The client's CFO estimated that direct costs had already crossed 4.1 billion VND, before counting the revenue lost from a loyalty program stuck on printed vouchers.
The problem the client faced
Our two-week audit surfaced three distinct issues, and the client's leadership team was surprised that only one of them was actually technical.
The first issue was scope drift disguised as feature growth. The original 42-page specification had ballooned to 118 pages, with new features being added by three different stakeholders without a formal change process. The mobile team was chasing a moving target, and the backend team was building endpoints for screens that had already been redesigned twice.
The second issue was a backend architected for a demo, not for production. The API layer had been built on a shared PHP monolith that also served the client's e-commerce website. Point-balance queries were pulling from a MySQL table with 2.1 million rows and no index on the member ID column. Redemption transactions were not idempotent, which meant a spotty 4G signal at the point of sale could double-charge a member's points. The previous vendor knew about these issues and had proposed a full rewrite as the third quote.
The third issue, and the most damaging, was a team that could no longer talk to itself. By month twelve, the client's product owner, the design agency, and the delivery vendor were communicating almost exclusively through a shared spreadsheet that nobody trusted. Meetings had become blame sessions rather than decision forums.
The technical debt was real. But it was the collapse of trust and communication that was actually preventing shipping.
The solution we delivered
We proposed a 90-day fixed-scope rescue with three clear phases, and we anchored the commercial terms to shipping the loyalty campaign before the client's peak retail season in November.
The first two weeks were about cutting, freezing, and re-baselining the plan. We negotiated a hard scope freeze with the client's leadership: the launch would ship 31 of the 118 features on the spec, chosen because they covered the three highest-value member journeys. Everything else went into a documented v1.1 backlog with owners assigned. This single decision recovered roughly six weeks of engineering time that would otherwise have been spent chasing edge cases nobody asked for.
The next six weeks were about rebuilding the risky pieces and keeping the rest. We preserved the existing React Native codebase, because the mobile team's work was actually reasonable and only poorly integrated. We then rebuilt the two components that were structurally broken. The loyalty API was extracted from the shared PHP monolith into a small Node.js service with its own PostgreSQL database, proper indexing on member lookups, and idempotent redemption transactions keyed by a client-generated UUID. We introduced a lightweight event log so that every point transaction was reconstructible from raw events, which meant the finance team could finally audit balances without opening a support ticket.
The final month was about stabilizing, testing, and handing over. We ran two rounds of closed beta with 200 real members across five stores, fixing crashes as they surfaced. We wrote runbooks for the client's incoming internal team, documented the deployment pipeline, and trained two of the client's own engineers to own the codebase after handover. The app shipped to both stores on day 87, three days ahead of the campaign deadline.
Technical highlights that made it work
Three engineering choices did most of the heavy lifting.
The idempotent redemption endpoint eliminated a class of double-charge bugs that had been damaging member trust for months. Every redemption request from the mobile client carried a UUID; the backend recorded the UUID in a unique index and rejected duplicates with the original response. A member on a bad 4G signal could now tap Redeem three times and only lose points once.
The event-sourced point ledger replaced a mutable balance field with an append-only log of point events. Balances became derived state, computed on read and cached. Reconciliation, which had been a monthly nightmare for the finance team, became a single SQL query. This is not a novel pattern; it is the correct pattern for anything involving money or money-adjacent value. The previous vendors had skipped it because it takes longer to explain than to build.
The decoupled build pipeline for iOS and Android cut release cycles from a week to under an hour. The two platforms no longer waited on each other, and a failing test on one stopped shipping only that platform. A small change on paper, but a significant morale shift for a team that had been shipping fear-first for a year.
Conclusion
A stalled software project is rarely stalled for a single reason. In this case, the technical debt was fixable in eight weeks; the communication breakdown was the harder problem, and it needed an outside team with the standing to say no to features and the credibility to defend that call to the CEO. The rescue worked because the client was willing to freeze scope and rebuild trust in parallel, not because we introduced any exotic technology.
If your team is looking at a project that is over budget, past deadline, and losing the confidence of your stakeholders, the answer is almost never another nine months and a bigger delivery team. It is usually a short, honest audit followed by a fixed-scope rescue plan built around one shipping event that matters to the business. That is the kind of work we take on at MerkTechs: bringing a fresh, experienced perspective to projects that have lost their way, and getting them back to production without the drama.