Case study
An AI operations platform for shipping and logistics
ShipmentOS: a shipping operations platform where branch staff run real work through an AI agent — creating shipments, itemising customs declarations, screening restricted goods and emailing customers about problems while they can still act on them, with every write gated in the backend.
- Client
- Mailboxes Etc (Fortidia)
- Year
- 2025
- Service
- Production AI Agents, AI Product Engineering, Systems Integration

Overview
Mailboxes Etc branches handle shipping, fulfilment, and print work for thousands of business customers. The operational reality was a lot of skilled people doing careful, repetitive data entry: rekeying addresses from emails, itemising customs declarations by hand, and checking tracking across several carrier portals.
The obvious answer — "add a chatbot" — would not have touched any of that, because none of the work is answering questions. It is doing things. So we built ShipmentOS, an operations platform where staff describe what they need in plain language and an agent performs the action against real systems, with the guardrails that makes safe.
Since then the interesting problems have moved outward. The agent no longer only acts inside the platform: it screens what a parcel contains against regulatory lists, generates the customs paperwork nobody at a counter has a template for, and — the sharpest edge — emails the branch's own customers about problems without a human in the loop.
Key challenges and solutions
Challenge 1: The work is write-heavy, and wrong writes are expensive
A shipment created with the wrong address or weight costs money and a customer relationship. Most AI systems avoid this by only reading. That would have solved nothing here.
Our solution: every action the agent can take is an explicit, typed tool with a validated schema — eight of them covering shipment creation, customer records, tracking, open actions, and bulk import and export. Write actions are gated server-side, so an approval requirement is enforced in the backend rather than requested in a prompt. Retries and duplicate submissions are handled explicitly, so an agent that runs twice does not create two shipments.
Challenge 2: Customs itemisation carries regulatory risk
International parcels need customs declarations with HS tariff codes and per-item weights. Getting a code wrong means held parcels and penalties — a failure mode where confident-sounding output is worse than none.
Our solution: the itemisation tool classifies contents and proposes HS codes with per-item weights, reconciled against the declared parcel weight, but is deliberately built read-only and non-persisting. It never writes the declaration. An operator reviews and applies it. The AI does the tedious part; the accountable human keeps the decision. This was a design choice, not a limitation.
Challenge 3: A parcel held at 7pm sat untouched until the next afternoon
A customs charge lands out of hours. Nobody sees it until someone opens the dashboard tomorrow, the customer pays that evening, and two days are gone — days in which the customer was perfectly able to act, and simply had not been told.
Closing that gap means letting the agent email the branch's customers unprompted. That is a much sharper edge than anything inside the platform: a wrong write can be corrected, but a wrong email has already been read.
Our solution: what may send without review is decided on reversibility, not model confidence. A type is auto-sendable only if it is something the customer would have had to deal with anyway, so being wrong costs a redundant email rather than a bad instruction. Two categories are deliberately excluded on that test — rebooking a delivery, which only the person expecting the parcel can do, and a stalled parcel, where chasing the carrier is the branch's job, not the customer's.
The decision itself is a pure function: no database, no network, and no model. It takes the classified event, the store's settings and the shipment's send history, and returns one of nine outcomes — send, queue for approval, or one of seven distinct suppression reasons — along with the rationale, which is written to the log in every case. Keeping it pure means the safety-critical ordering rules are deterministic and inspectable rather than an emergent property of a prompt.
Around it sit the blunt limits: at most three automatic emails per shipment, a 24-hour cooldown per type and six hours across all types, a duplicate block, and two independent switches that must both be on — the type must be auto-sendable and the branch must have enabled it. The whole system is off until a branch turns it on. Emails never state an amount, a date or a deadline; they say what kind of action is needed and link to the carrier's own page, so the carrier stays the source of truth and a misread event costs a redundant email rather than a wrong number.
Challenge 4: "No results" and "no data" are not the same answer
Screening a parcel against prohibited and dangerous goods lists is a question where the dangerous failure is not a wrong match — it is a confident silence. A destination with no list held looks identical to a destination that cleared, unless you build the difference in.
Our solution: contents are screened in one pass against two references — all nine IATA hazard classes, written around what actually crosses a counter (the perfume, the power bank, the can of deodorant), and the destination's own restricted-goods list, held for 202 of 218 destinations. Findings are graded, quote the wording that triggered them, and name the list and effective date they came from. Where no list is held, the panel says so in those words. A no-match is never rendered as clearance, and the screening record is dated and marked stale the moment the declared contents change.
Challenge 5: Most of a legal document should not be generated
A parcel stops at customs and the carrier asks for a document nobody at the counter has a template for. It is tempting to point a model at the problem. But these are signed declarations, and on some of them the form itself names the offence for signing falsely.
Our solution: six documents are generated from the shipment — power of attorney, personal effects declaration, importer-ID request, declaration of value, non-dangerous-goods declaration, and a clearance cover letter. Five are entirely deterministic templates with no model involved. The sixth, the cover letter, is the one place a situation genuinely differs per hold; even there the model writes two short paragraphs into fixed slots, is never told who any party is, and if it is unavailable, refuses, or fails the output guard, a deterministic skeleton is used instead and no model is recorded against the document.
The generator also refuses. Producing the non-dangerous-goods declaration for a real shipment whose only item was a used iPhone would have asserted "no lithium cells or batteries" directly above a line reading "Used iPhone 12" — signed, a false declaration. A pure pre-check now blocks it, and runs at render time rather than trusting that someone screened first.
Challenge 6: Vendor lock-in was an unacceptable risk
A platform this central to daily operations cannot be one provider's outage or price change away from a crisis.
Our solution: model selection is configuration, not code. Seven models are available through a gateway with per-provider fallback ordering, and the platform probes model capabilities — tool support, vision, reasoning — at runtime rather than assuming them. Switching models is a config change, and the default has already moved once without a deploy-shaped migration.
What we did
- Production AI agents
- Typed tool design
- Server-enforced approval gates
- Autonomous customer notification
- Regulatory screening
- Deterministic document generation
- Carrier API integration
- Multi-provider routing
- Next.js & PostgreSQL
What shipped
- 8
- Shipping tools taking write actions
- 202
- Destinations with a restricted-goods list held
- 7
- Models with per-provider fallback
- Server
- Where approval gates are enforced
Tracking runs on first-party FedEx and UPS OAuth integrations with an AfterShip fallback for carriers without a direct one, unified behind a single interface and re-checked on a two-hourly schedule.
Alongside the platform, we built and maintain the marketing sites for five high-potential branches — Euston, City Fulfilment, Holborn, Highgate, and Fleet Street — on Next.js and Tailwind, each optimised for local search in its own catchment.
Conclusion
The interesting part of this project was never the language model. It was deciding, action by action, which things an agent should be allowed to do on its own, which need a human to approve, and which it should only ever propose — and then pushing the answer down into code that cannot be talked out of it. The rules that decide whether a customer gets an email, whether a screening result counts as clearance, and whether a declaration may be signed are all plain deterministic functions sitting underneath the model, not instructions given to it.
That is the work that separates an AI system you can put into daily operations from one that stays a demo.
More case studies
Next step
Tell us what you’re trying to build
Most engagements start with a fixed-price audit, so the first thing you buy is a decision rather than a commitment.