The build story
The work already arrives. Somebody just retypes it.
A line goes down, a cylinder leaks, a unit has no hot water. It reaches the firm the way it always does: as an email, from a person, with a photo or a PDF attached. Then somebody reads it, works out how bad it sounds, retypes it into a job, decides who is nearest, and writes back. That is a skilled person doing data entry, in every trade, every property manager and every facilities team in the country.
WorkOrders removes the retyping. It reads the message and everything attached to it, writes the job, works out how urgent it is, puts it in front of the whole crew, and answers the customer back. The whole product is one sentence: the email comes in, and the work order writes itself.
Starting again on purpose
I had a version of this in 2025. A pricing page, some screens, and then a year of nothing. When I came back to it I deleted the lot and scaffolded an empty Laravel 13 app instead, because starting again was cheaper than reading what I had written the first time. Six weeks later: 402 commits, 34 models and 3,874 tests.
The one thing worth carrying forward was the shape of the problem. Multi-tenancy, the intake pipeline and the field app are decisions you make once, on day one, and live with. Building them first, on a clean floor, is why the six weeks went where they did.
Different work, same first move
A breakdown on a production line and a leaking cylinder in a rental have almost nothing in common, except that both turned up as somebody’s email. So this is not five products with five feature sets. It is one product and five readings of the same sentence: manufacturing, construction, field services, property management, and the IT team whose tickets were always going to arrive as email whatever the company bought.
That set of drawings is the part of the design I am most pleased with, and it is the least automatic. Every one is a component with a shared frame, a shared grid and a shared accessibility contract, so a new industry is a new drawing rather than a new stylesheet. The trade a firm picks at signup also seeds their trial with sample work, so the first screen is a board that resembles theirs instead of an empty one.
One call, and a switch to turn it off
A single call reads the email and returns the whole job: a one-sentence summary of the work, the thread as clean text, an urgency, and the customer and site address if the message actually said them. The schema is enforced by the provider, and the instructions are blunt about the failure that matters: answer only from what the email says, return null rather than filling a field in, and never infer an address from a signature block. A job that quietly invents a street number is worse than one with a blank on it.
Around that sit the guardrails, all of them unglamorous. A master switch, and off means no provider is ever called and the panels are hidden, so the product behaves exactly as it does when a vendor has an outage. A content-addressed cache, because maintenance email repeats itself and the same building reports the same fault every winter. A separate queue, so a backlog of enrichment can never delay the mail. Per-feature models, so a pass over every inbound message runs cheap while a person asking a question about a job can justify better.
And no provider is named anywhere in the application. The owner sets a key and a name in the environment, and Gemini, Anthropic or OpenAI all work. Models are the fastest-moving dependency any of us have; wiring one in by name is how you date a product in a year.
Nobody hands the work out
The old way to dispatch is to decide who is free, ring three people to find out, and then wonder why the job is unread. Here a new job goes to every phone at once and the first person to tap accept owns it. It drops off everyone else’s list, and a second tap is told it has already gone. Two people cannot take the same job.
That one rule deletes a category of office work, and it is why the phones are native rather than a website in a shell. A plant room has no signal, so losing the connection has to keep the list on screen and show a notice rather than dumping somebody back to a sign-in form under a building. Both apps are built against the product’s own documented, versioned REST API rather than a private one, so the field surface is tested and specified the way a public surface is.
Built so the next thing does not need me
Every firm gets its own address, yourfirm.workorders.nz,
rather than one shared front door. That is a tenancy decision before it is a branding one: a
session cookie belongs to the host that set it, so a contractor working for two firms holds
two independent sessions in two tabs instead of one account to be switched between. Rows are
scoped by company, every route declares which hosts it may answer on, and an architecture
test walks every model in the codebase and fails the build unless it is either scoped or
exempted with a written reason beside it. Tenancy that depends on everyone remembering is
not tenancy.
The same instinct runs through the rest of it. There is a public API with per-company keys and idempotent creates, so a facilities platform can raise jobs directly instead of being told to send an email. Customers get a portal reached from a link in their own status email, with no account and no password, because asking a tenant to sign up to report a leak is how you stop hearing about leaks. Inbound mail is never deleted, only filed, so nothing is unrecoverable when a parse goes wrong.
And billing carries a guard most products would not need. This runs on a Stripe account shared with several other things I have built, and a webhook endpoint is not scoped to the app that created it: every endpoint receives every event of the types it subscribes to, from every product. There is no setting that changes it, so one predicate decides whether an event is ours before anything is written, and anything foreign gets a polite 200 and is dropped. Getting that wrong on a free game sends a stray notification. Getting it wrong here cancels a paying company’s access.
The interesting work from here is not features. It is the second firm, and the tenth, each arriving with their own trade, their own inbox habits and their own idea of what urgent means, all landing on the same board.
Running your business on software nobody dares replace? Here’s how we could work together.