The people doing the accounts are the people answering the messages, and they only have a phone
A whole back office on a phone (inbox, orders, inventory, finance and HR) served by a modular API, for a business whose staff have never sat at a desk and aren't going to start.
The shape of the work
- Industry
- E-commerce & Retail
- Duration
- 24 weeks
- Cooperation model
- Dedicated team
- Services
- Mobile engineeringAPI platformBack office
- Integrations
- Social platform APIsPush notificationsObject storagePayroll exportError tracking
- Technologies
- React NativeExpoNode.jsMongoDBWebSockets
- Team
- 1 Engagement lead2 Mobile engineers2 Backend engineers1 QA engineer
Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.
The hard problem
Everything past the sale was happening in spreadsheets on one manager's laptop. Stock counts, staff hours, cash reconciliation and payroll all lived in files nobody else could open, and the business couldn't answer a question about last month without that person being available.
The files were worse than inconvenient: they were the only copy. Nobody else could open them, several were interlinked in ways only their author understood, and the business's entire operational memory depended on one person's availability and one machine's disk. That's a continuity risk before it's a tooling problem, and it had just been demonstrated.
A single application covering the whole operation, and an API composed of independent modules. Inbox, orders, inventory, finance and HR each mount on their own, which is what let us ship the app one module at a time instead of in one release nobody could test.
How the pieces fit
- 01
Mounted API modules through a registry, so each area ships and is tested on its own
Composing from modules was decided in the first week, and it's why the app shipped in five releases instead of one nobody could have tested.
- 02
Put the daily surfaces in tabs and the configuration surfaces behind a settings tree
The tab bar was sized by watching two weeks of real use instead of reading the org chart, and the two disagreed about where finance belonged.
- 03
Used a socket connection for the inbox only, and kept the rest of the app off real-time
Restricting the socket to one surface was measured: an all-live build cost about a fifth of battery over a shift and delivered freshness nothing needed.
- 04
Kept authentication providers, OTP and sessions as their own module, out of the feature code
Providers, OTP and sessions live in their own module, so adding a sign-in method touches one place instead of five screens.
- 05
Documented the API from the same definitions the routes use, so the spec can't drift
Generating the spec was worth doing because the previous documentation had described three endpoints that no longer existed and omitted five that did.
The system we were asked to build
The retailer sells through social channels and employs around thirty people, none of whom work at a desk. We built the phone application and the API behind it: a shared inbox, orders, inventory with brands, categories and taxes, cash and accounts, and the HR functions that keep thirty people paid.
A retailer selling through social channels with about thirty staff, none of whom work at a desk. Everything past the sale (stock counts, hours, cash reconciliation, payroll) lived in spreadsheets on one manager's laptop. The engagement was commissioned during a two-week stretch when that manager was on leave and the business couldn't answer a question about the previous month.
Full-Stack Engineering
Modules mount independently
A registry composes the API, so each area could ship and be tested without waiting for the others.
The API is composed from a registry of modules that mount independently (inbox, orders, inventory, finance, HR), each with its own routes, models and tests. That's what let the application ship one area at a time and be tested one area at a time, instead of arriving as a single release nobody could meaningfully verify.
- A registry composes the API from independent modules
- Each module ships and is tested on its own
- No single release too large to verify
Products, brands, categories and tax rates behind settings, because stock is configured once and read every day: a skirt out of stock, a zero-rated children's dress, and 186 products across two VAT rates.
The day's orders filtered by state (unpaid, packing, sent, delivered, cancelled), here narrowed to the six unpaid ones, with the amount reading down the right edge and the total still to collect above them.
Tabs for daily work
Inbox, orders and reports are tabs; inventory, finance and HR configuration sit behind settings where they belong.
Inbox, orders and reports are tabs because they're opened many times a day; inventory maintenance, finance configuration and HR sit behind settings because they're opened weekly at most. The tab bar was sized by observed frequency, not by organizational importance. The two disagree, and the interface should follow the first.
- Tabs sized by observed daily frequency
- Weekly work moved behind settings
- Frequency beats organizational importance in the layout
Sockets only where they earn it
The inbox is live. Nothing else is, because a real-time app that doesn't need to be is a battery problem.
Only the inbox holds a socket. Everything else polls on demand or refreshes on focus, because a real-time connection the product doesn't need is a battery cost the user pays all day for nothing. The socket reconnects with backoff and falls back to polling, so the inbox never sits there dead.
- A socket only where the product needs one
- Everything else polls on demand or refreshes on focus
- Falls back to polling, so the inbox never goes dead
The one live surface: a conversation that became an order, the order card raised inside the thread, and the customer typing as it happens. The strip says it falls back to polling if the connection drops.
Counting the drawer note by note against the session's expected cash, which is the spreadsheet this replaced: £600.90 expected from float, cash sales, refunds and payouts, £600.90 counted, balanced.
Auth as its own module
Providers, OTP and sessions are separate from feature code, so adding a sign-in method touches one place.
Providers, OTP and session handling live in their own module separate from feature code, so adding a sign-in method touches one place instead of appearing across five screens. Sessions are refreshed centrally, which removed the inconsistency where two features disagreed about whether a user was still signed in.
- Providers, OTP and sessions in a single module
- A new sign-in method touches one place
- Central refresh, so features can't disagree about session state
Docs from the route definitions
The monthly run for thirty staff who never see an office: hours from attendance, gross, tax and net pay, and the six people still waiting to be paid.
The API specification is generated from what the routes actually declare, so it can't describe an endpoint that no longer exists.
The API specification is generated from the route definitions themselves, so it describes what is actually mounted, not what someone last wrote down. A removed endpoint disappears from the docs by being removed from the code, which is the only mechanism that keeps documentation honest once a project passes its first month.
- Spec generated from the route definitions
- A removed endpoint disappears from the docs automatically
- Documentation can't describe what no longer exists
Phase by phase
Phase 1: The Module Registry
An API You Can Ship In Pieces
Built the backend around a registry that mounts feature modules independently. That decision is why the phone app could go out with inbox and orders while finance was still being written.
- Module Registry
- Shared Context
- API Documentation
Phase 2: Inbox And Orders
The Daily Surfaces
Built the shared inbox over a socket connection and the order flow beside it, because those two are what somebody opens the app for twenty times a day.
- Shared Inbox
- Orders
- Live Updates
Phase 3: Inventory
Brands, Categories, Taxes
Built the inventory module (products with brands, categories, units and tax rates) as a configuration surface behind settings, since it's set up once and edited rarely.
- Products
- Brands & Categories
- Tax Rates
Phase 4: Finance
Accounts, Cash, Transactions
Built accounts, the cash register and the transaction ledger, replacing the reconciliation spreadsheet that only one laptop could open.
- Accounts
- Cash Register
- Transactions
Phase 5: People
Thirty Staff, No Office
Built attendance, shifts, leave, departments, designations and payroll: the functions a thirty-person business needs and had been running from a file.
- Attendance & Shifts
- Leave
- Payroll
A stock-out after the sale: the skirt and one set of clips that came off the order, the £51.50 sent back to the customer's bank account, and the order now worth £72.45 of the £123.95 paid.
What it carries now
All
Spreadsheets replaced
6
API modules
0
Desks required
1
Live surfaces
Spreadsheets replaced is all of them. API modules is a count. Desks required is zero, describing the operating model, not a measurement. Live surfaces is one: only the inbox holds a socket, and that's a deliberate decision, not a limitation.
Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.
What the architecture settled
- 01
A business without desks needs its back office on a phone, not a mobile view of a desktop console.
A business with no desks needs a back office designed for a phone, not a desktop console reflowed. The difference shows up in every screen that assumes a keyboard.
- 02
Compose the API from modules and you can ship an app in stages; write it as one route file and you can't.
Modular composition is what made staged delivery possible; one route file would have forced a single release that nobody could have tested in pieces.
- 03
Make only the surface that needs to be live actually live. A fully real-time app spends battery to deliver nothing.
Making only the inbox live was a considered decision: sockets everywhere would have drained battery all day to deliver freshness nothing else needed.
- 04
Knowledge on one person's laptop isn't a tooling problem until they take leave, and then it's the only problem.
Knowledge on one laptop is invisible as a risk until that person takes leave. Then it isn't one problem among several; it's the only one.
How the work was run
We shipped module by module, and their operations manager chose the order: inbox and orders first, then inventory, then finance, then HR. That sequencing came from her, and it's why the app was in daily use before half of it existed.
A dedicated team for twenty-four weeks, with the API composed from mounted modules from the first commit. That's what let the app ship one area at a time instead of arriving as a single release nobody could meaningfully test. Staff used each module as it landed, which is how the tab bar ended up sized by observed frequency and not by org chart.
One order, five modules
From a chat to a partial refund, with the money agreeing at every step.
Follow one order through the suite: the conversation in the live inbox, the order tagged onto it, the payment matched, a stock-out at packing and the refund that follows. Each stage names the module that handled it, and the order value and refund figures update as you move. Switch stages, or use the arrow keys once one is focused.
Order MT-20417 · from the chat to the partial refund
Customer, prices and times are illustrative; the arithmetic is exact- Hi, can I get the sage wrap dress in a 12, the oat pleated skirt in a 10 and two sets of the beaded clips?12:58
- All showing in stock. Raising the order for you now.13:03
- Order MT-20417 raised from this thread · 13:03
- Transfer sent13:40
- Payment matched · invoice INV-3301 · 13:42
- So sorry, packing found the skirt and one set of clips are not on the shelf. Refunding those now.14:18
- Refund sent to the paying bank account · 14:21
- No problem, thanks for sorting the refund14:27
- Linen wrap dressSage · 12 · 1 × £58.00Packed£58.00
- Pleated midi skirtOat · 10 · 1 × £42.00 · 0 of 1 on the shelfShort£0.00
- Beaded hair clipsSet of 3 · 2 × £9.50 · 1 of 2 on the shelfShort£9.50
- Delivery£4.95
Paid £123.95 − refunded £51.50 = order now worth £72.45
The short lines come off the order, the refund goes back to the account that paid, and the order is re-priced. What was paid, minus what was refunded, is what the order is now worth.
A back office composed from modules, live only where it has to be
The API is a registry of modules, not one file of routes, which is what let the app ship one area at a time. Only the inbox holds a connection open; a real-time app that doesn't need to be is a battery cost paid all day.
- 01 · SourceMessages from every social channelEvery channel lands in one shared inbox, so a conversation that becomes an order is tagged where it happened.
- 02 · TransportOne socket, for the inbox only1 live surface. It reconnects with backoff and falls back to polling rather than showing a dead inbox; everything else polls on demand or refreshes on focus.
- 03 · Business logicModule registry · 6 modulesInbox, Orders, Inventory, Finance, People, Sign-in. Each mounts independently with its own routes, models and tests; the API spec is generated from the same route definitions.
- 04 · StateMongoDB behind a Node.js APIStock, hours, cash and payroll are records the whole team reads, not files on one manager's laptop.
- 05 · ClientReact Native app, built with Expo0 desks. Daily surfaces are tabs sized by observed use; inventory, finance and HR sit behind settings.
What a back office on a phone must not get wrong
Cash, pay & stock that agree with each other
Money that has to match
The drawer is counted note by note against the session's expected cash, float plus cash sales less refunds and payouts, so a variance shows before the session closes. That count used to be a reconciliation spreadsheet only one laptop could open.
30 people paid from the same records
Attendance, shifts, leave and payroll live in the people module instead of a file, so the monthly run shows hours, net pay and who is still waiting to be paid, and anyone with access can answer for last month while the manager is on leave.
Sold stock that isn't on the shelf
When packing finds a line missing after the sale, the line comes off the order, the refund is the value of what was removed, and the order is re-priced, so what was paid, what went back and what the order is worth always agree.
Is your back office still a spreadsheet on one person's laptop? Scope your build in 3 minutes.
Scope your buildNearby engagements
Data & AnalyticsA checkout that stopped losing sales to a 6-second load
Profiling found the real bottleneck behind a slow checkout (an N+1 query and an oversized bundle) and tuned both, with before-and-after metrics locked in as a baseline against future regressions.
E-commerce & Retail · 5 weeks
Web PlatformsA reader people finish, and a library that remembers where they stopped
A reading platform for a comics catalog: a browse surface people can actually navigate, a reader that gets out of the way, and a history that puts everyone back on the page they left.
E-commerce & Retail · 18 weeks
Web PlatformsOne number, a whole handset, and a database that keeps up with 125 brands
A metered IMEI lookup service that turns fifteen digits into a device, its specifications and its status. It's sold three ways to three audiences and backed by a device database that maintains itself.
Consumer Electronics · 22 weeks
Let's talk
Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.














