Skip to content
Husain Bootwala
05Shipped

Laundry Tracker

QR-tagged garment intake for a laundry that was running on memory and paper slips

Channel
05
Period
2026
Domain
Software
Role
Sole engineer: data model, backend, counter UI, QR and receipt pipeline, Windows and macOS packaging

Stack

  • FastAPI
  • Python
  • SQLAlchemy
  • SQLite (desktop) / Supabase Postgres (hosted)
  • Jinja2
  • QR generation and scanning
  • OpenWA WhatsApp gateway
  • Playwright
  • PyInstaller
  1. A pile of shirts and a handwritten paper slip, with no link from either back to the household that owns themPilePaper slipHousehold?No record
    BeforeMemory, chalk and paper slips. No record.
  2. The Laundry Tracker dashboard: households, clothes tracked, missing alerts, due soon and water saved, beside a table of overdue garments by household. Demonstration data.
    AfterEvery garment tagged, timed and traced.
Steps, intake to receipt
7
API endpoints
65
Desktop builds
2
Auto-sent messages
0

Intake to receipt

  1. A printed sheet of QR tags for one household, with one tag carried across and fixed inside a shirt collarUnit 0804QR sheetTagged garment
    FixQR tags printed per household, one per garment.
  2. A handheld scanner reads a garment tag, which starts a three-day return clock shown on the garment’s row as 2 days 18 hours leftScannerScan inD0D1D2D3DueClock onShirt08042d 18h left
    FixScan-in starts a return clock. Overdue raises an alert.
  3. A handwritten laundry slip beside the generated receipt, which keeps the same rows and wash, dry-clean and press columnsWDPHandwrittenOrder 00102WDPSheetTopiTowelGenerated
    ResultThe receipt keeps the shop's own slip layout.
  4. A drafted WhatsApp message with the receipt attached, sent to the customer’s phone only when a staff member clicks sendDraftedYour orderis readySendSent by staffCustomerNever on a timer
    gateThe system drafts the message. A person sends it.
  5. A single counter PC launching one installer that bundles the app, a local database file and the WhatsApp gateway, for Windows and macOSOne PCNo ITOne installerAppLocal DBWA gateway.exe.app
    shipOne installer. No Docker, no IT.

On the counter

  1. The Generate QR Codes screen: count, colour and print size, a layout panel for label size, padding, gaps and cut lines, and a live preview of the printed sheet.
    printTag sheets laid out for the shop's printer.
  2. A household's garments in a table: barcode, cloth type, a timer such as 2d 18h left, last scan, and per-item actions. Demonstration data.
    trackOne row per garment, with its time left.
  3. The Cloth Types page: kurta, saya, topi, pants, shirt, socks, towel and underwear, each with a litres-saved-per-wash value.
    catalogueCloth types set the water saved per wash.
Read the engineering detail

The problem

A laundry handling dozens of households a day tracks garments by memory, chalk marks and paper slips. Items get separated from their batch. When a customer says a shirt is missing, there is no record on either side. Two people have different recollections, and the shop pays for the shirt to keep the customer.

What I built

Every item gets a printed QR tag at intake, bound to a household. Tags are read by a camera or a USB barcode scanner. The counter UI creates or looks up a household, scans its garments into a batch, and seals the batch when the bag leaves the counter. Each garment then carries its own return window. Anything past it surfaces as a missing-item alert instead of being discovered through a complaint. Scanning a garment out on return closes it, completes the batch when the last one lands, and counts the water saved for its cloth type.

The receipt matches the format the shop already used. The customer gets a WhatsApp message that the system composes and a person sends.

It runs as a desktop app on the counter of a laundry in Africa, which is where it is used today.

The constraint that shaped it

The shop counter is one PC, run by staff who are not going to install Python, and the internet connection is not guaranteed.

That drove the architecture. The application is a FastAPI service with server-rendered pages, so there is no build step and no node_modules on a shop counter. PyInstaller packages it into a Windows .exe and a macOS .app with a launcher, so it starts from an icon on the desktop and opens in the browser. The desktop build runs on a single SQLite file in the operating system’s user-data folder, so the install stays read-only and the data stays writable. The same code runs against Supabase Postgres when hosted.

Sending receipts in bulk

A single send is one click per household, so staff can also select several finished batches and send them together. The messages go through OpenWA, a self-hosted WhatsApp gateway, with the receipt rendered to an image by headless Chromium. Sends are throttled with a few seconds and some random jitter between them, one failure never stops the rest, and a batch that has already been sent is skipped unless staff ask to resend. It is still a person who picks the batches and presses send.

OpenWA drives WhatsApp through an unofficial client, not Meta’s paid API, so the sending number can be restricted. The spec accepts that risk explicitly: a dedicated number, throttled sends, and only real receipts to customers who expect them.

The gateway, its Node runtime and the Chromium build all ship inside the installer. The shop installs one thing, with no Docker and nothing to download.

What I learned

This is the least technically ambitious project here and the one I learned the most from, because the hard constraints were all non-technical: what the staff will tolerate, what breaks when the shop is busy, and how a system fails when the person using it is in a hurry.

Media

A seven-step flow in two lanes. Before anything is tracked: register the household, print the barcode tags, tag each garment. Then, every visit: scan garments into a batch and seal it, wash while each garment's return window runs, scan out on return, which completes the batch and is when water saved is counted, and finally generate the receipt. The last step is highlighted as a human gate: the WhatsApp message is composed by the system but released by the person at the counter. Each step is labelled with the record state it produces.
Diagram · Intake to receipt, and the one step that stays human
Video · Intake from scan to printed receipt
A generated Safari Laundry receipt for order 00102, itemising bedsheet, topi and towel with wash, dry-clean and press ticked. Above it are buttons to print or save as PDF and to send via WhatsApp, and a line recording that the receipt was generated at 07:26 and the WhatsApp sent at 07:27.
Screen capture · The generated receipt and its WhatsApp send

How this was built

The spec sheet above lists the stack. This section covers how the work was run, which a screenshot cannot show you.

Where AI did the work

  • Generated the schema, ORM models and validation layer as one coherent pass so the database constraints and the Pydantic schemas enforce the same rules instead of drifting apart.
  • Kept a running progress log across sessions, so each one resumed from the recorded state of the build instead of a guess at it.

The discipline around it

  • Architecture and setup are separate documents from the README: one for someone extending the code, one for someone trying to get it running on the shop counter.
  • Receipt rendering was built against photographs of the receipts the shop already used, rather than a layout I invented.
  • Larger features started as a written spec with their decisions and known risks, agreed before any code was written.

What stayed human

  • Nothing auto-sends to a customer. The WhatsApp message is composed by the system and released by the person at the counter, because a wrong automated message to a customer is worse than no message.
  • Barcode uniqueness is enforced at the database level rather than in application logic, since a duplicate tag would undo everything the system is for.

Process artifacts in the repo

CLAUDE.md
The working agreement for AI sessions, covering the version history, discuss-before-build and manual migrations.
tasks_wip/
One spec per feature, with decisions, risks and phases, written before the code.
release_steps.md
The runbook for building and handing over the Windows .exe and macOS .app.