Get in touch

From the workshop

WorkOrders

The email comes in, and the work order writes itself.

The job already arrives as an email. This reads it, and everything attached to it, writes the work order, works out how urgent it is, and puts it in front of the whole crew. First to accept owns it. Multi-tenant Laravel for manufacturing, construction, field services, property and IT, with native iOS and Android apps for the van.

In beta Web iOS Android
Laravel Livewire SwiftUI Kotlin Email intake Urgency triage Stripe
The build story

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.

Two technical drawings in the WorkOrders style: a hopper-fed machine with four numbered service points, and a hot water cylinder with its gate, TPR and stop valves called out.
The drawings are hand-authored SVG in the vocabulary of a real service manual: dimension lines, leaders, balloons, and one part picked out in the brand colour. Stock icons would have said nothing about the work. These say the software has been in a plant room.

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.

The field app showing a job for no hot water at both units of a Petone address, marked high urgency, with a note that tenants are home from 3pm and a green confirmation that the job is now assigned to this technician.
The technician gets the original email, the photos and the PDFs, not a one-line dispatch note. Three taps move a job, and each one lands in the office as it happens.

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 office dashboard for a plumbing firm: counts of new, assigned, ongoing and completed jobs, a fortnight of raised against completed work, and open jobs listed by technician.
Every number on the office board is a link to the jobs it counted. A figure you cannot open is decoration.

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.

Visit WorkOrders