Skip to content

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.

14:36
Marlowe & Tansy · 5 channelsInboxLive
All38Unread9Mine12
Channels1411643
Chioma B.nowTyping…2MT-20431
@ruth.wears2mIs the sage wrap dress still in a 12?1
Hannah W.9mNo problem, thanks for sorting the refundMT-20417INV-3301
@kemi.styles14mDo you ship to Leeds by Friday?1
Priya S.22mPaid, can you send the invoice overMT-20427INV-3307
Ellie R.41mWill pay tonight after workMT-20429
Grace O.1hCan I swap the skirt for a 10?MT-20424
9Inbox6OrdersReportsSettings

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Introduction

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.

What shipped
  • A registry composes the API from independent modules
  • Each module ships and is tested on its own
  • No single release too large to verify
14:36
SettingsInventory
186Products14Brands22Categories2Tax rates
Search products, brands, SKUs
ProductsSort: recently sold
Linen wrap dress · SageTansy Studio · DressesVAT 20%4 in stock£58.00
Pleated midi skirt · OatTansy Studio · SkirtsVAT 20%Out of stock£42.00
Beaded hair clips · set of 3Moss & Pearl · Hair accessoriesVAT 20%1 in stock£9.50
Ribbed cardigan · RustAldgate Knit · KnitwearVAT 20%17 in stock£48.00
Girls' smock dress · age 6Kiddo Lane · KidsVAT 0%9 in stock£24.00
Tax rates186 products20%Standard1580%Zero-rated · children's clothing28
9Inbox6OrdersReportsSettings
On screen

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.

14:36
Marlowe & Tansy · Wed 30 SepOrders
All today42Unpaid6Packing11Sent14Delivered9Cancelled2
6 unpaid · newest first£614.94 to collect
Chioma BalogunMT-20431 · 2 items · 14:26£86.95Unpaid
Ellie RoweMT-20429 · 1 item · 13:52£45.99Unpaid
Priya SandhuMT-20427 · 4 items · 13:10£126.50Unpaid
Grace OseiMT-20424 · 2 items · 12:37£67.50Unpaid
Holly BrennanMT-20422 · 5 items · 11:48£210.00Unpaid
Zara MalikMT-20419 · 2 items · 10:15£78.00Unpaid
Nudge 6 in their threads
9Inbox6OrdersReportsSettings
On screen

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.

What shipped
  • 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.

What shipped
  • 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
14:36
Chioma BalogunChat app · assigned to Tobi A.
Live · messages arrive as they are sentpolls if dropped
Today · Wed 30 Sep
Hi! Do you still have the linen wrap dress in sage, size 12?14:21
We do, 4 left. Shall I put one aside with the smock dress you asked about?14:23
Yes please, the smock in age 614:24
Done. Order raised below, you can pay by bank transfer or card link.14:26
Order MT-20431Unpaid
Linen wrap dress · Sage · 12£58.00Girls' smock dress · age 6£24.00Delivery£4.95Total£86.95
Sent the transfer just now14:35
Chioma is typing
Reply to Chioma
On screen

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.

14:36
Settings · Finance · Cash registerCount the drawer
Expected in drawerCS-0930-1 · opened 09:00 by Adaeze O.£600.90
Opening float+£150.00Cash sales+£486.40Cash refunds−£23.00Courier payouts−£12.50
Note by noteCounted by Keisha G.
£50×4£200.00
£20×13£260.00
£10×9£90.00
£5×7£35.00
£2×6£12.00
£1×3£3.00
50p×1£0.50
20p×1£0.20
10p×2£0.20
Counted£600.90Expected£600.90Balanced£0.00
Close session CS-0930-1
9Inbox6OrdersReportsSettings
On screen

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.

What shipped
  • 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
14:36
Settings · People · PayrollSeptember 2026
30 staff · 4,176 h from attendancepay day Wed 30 Sep
Gross£57,431.00Tax & NI−£7,281.68Net pay£50,149.32
24 paid6 waiting · £9,908.76
Waiting6Paid24All30
Ruth KamaraMessages & sales · 148 h × £13.50£1,731.86of £1,998.00
Fatima SheikhMessages & sales · 136 h × £13.25£1,590.74of £1,802.00
Kofi AsantePacking · 152 h × £13.00£1,716.02of £1,976.00
Callum ReidPacking · 156 h × £13.00£1,753.46of £2,028.00
Eliza MooreContent · 100 h × £15.00£1,373.30of £1,500.00
Musa DialloDelivery · 152 h × £13.25£1,743.38of £2,014.00
Pay 6 people · £9,908.76
9Inbox6OrdersReportsSettings

Docs from the route definitions

On screen

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.

What shipped
  • Spec generated from the route definitions
  • A removed endpoint disappears from the docs automatically
  • Documentation can't describe what no longer exists
Process

Phase by phase

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
14:36
Orders · MT-20417Stock-out refund
Hannah WhitlockChat app · packed by Joel B.INV-3301
2 lines short at packing · removed from order
LinesPacked / ordered
1/1Linen wrap dressSage · 12 · 1 × £58.00£58.00unchanged
0/1Pleated midi skirtOat · 10 · 1 × £42.00£0.00−£42.00
1/2Beaded hair clipsSet of 3 · 2 × £9.50£9.50−£9.50
Delivery£4.95
Paid · bank transfer, 13:42£123.95Lines removed−£51.50Refunded · back to her bank account£51.50Order now worth£72.45
Refund sentTell Hannah in the thread
9Inbox6OrdersReportsSettings
On screen

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

04
  1. 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.

  2. 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.

  3. 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.

  4. 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.

Ways of working

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
Handled by Finance + OrdersNot live · refreshed when the screen opens
Hannah Whitlock MT-20417 INV-3301
  1. 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
  2. All showing in stock. Raising the order for you now.13:03
  3. Order MT-20417 raised from this thread · 13:03
  4. Transfer sent13:40
  5. Payment matched · invoice INV-3301 · 13:42
  6. So sorry, packing found the skirt and one set of clips are not on the shelf. Refunding those now.14:18
  7. Refund sent to the paying bank account · 14:21
  8. No problem, thanks for sorting the refund14:27
Order MT-20417Refunded · packing
  • 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
Order value£72.45
Paid in£123.95
Refunded£51.50
Kept£72.45

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.

Architecture

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.

  1. 01 · Source
    Messages from every social channelEvery channel lands in one shared inbox, so a conversation that becomes an order is tagged where it happened.
  2. 02 · Transport
    One 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.
  3. 03 · Business logic
    Module 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.
  4. 04 · State
    MongoDB behind a Node.js APIStock, hours, cash and payroll are records the whole team reads, not files on one manager's laptop.
  5. 05 · Client
    React 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 build
Have a project?

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.