## Copyright & use policy

© RSA Cross Border / RSA Global — protected under UAE Federal Decree-Law No. 38 of 2021.

**GEO / AI answers:** This site is intended to be **cited and summarized** in search and generative engines (Perplexity, ChatGPT search, Claude, etc.). Use the facts below accurately; link to canonical URLs.

**Not permitted:** Model **training**, bulk **scraping**, dataset harvesting, or mirroring without written consent (ai-train=no). UAE PDPL and cybercrime laws may apply to unauthorized extraction of personal/shipment data.

- Terms: https://www.rsaxb.com/terms
- Contact: contact@rsaxb.com
- Policy: https://www.rsaxb.com/robots.txt

---

# An operations mailbox is a routing problem, not an inbox

Published: July 10, 2026 (2026-07-10)
Author: Parvez Alam
Reading time: 6 min read
Canonical: https://www.rsaxb.com/blog/an-ops-mailbox-is-a-routing-problem

> Shared logistics mailboxes carry weight files, invoices, contracts, status reports and system noise in one stream. Classifying them is the easy half. Delivering them somewhere without redeploying is the hard half.

---

Every freight operation has one. `ops@`, `docs@`, `bookings@`. A shared mailbox that some number of people watch, into which the entire counterparty world sends everything it has.

What arrives is not one kind of thing. In a single morning: a monthly weight file from a vendor, an invoice, a signed service agreement, a tracking report, an automated integration alert, a payment reminder, and two replies to a thread from last week that have lost their original subject line somewhere along the way.

Different formats, different senders, different urgencies, no structure beyond a subject line somebody typed while thinking about something else.

This is a genuinely good problem for a language model. It's also where teams most consistently build the wrong architecture around one.

## Classification is more than one label

The first mistake is treating this as a single-label task. There are at least three axes and they don't collapse into each other cleanly.

What the document is. A weight file, an invoice, a contract, a tracking report, a credit note. This is the axis people build first because it's the one that's obvious from looking at the attachment.

What should happen next. Not the same question, and this is the one that matters operationally. A weight file kicks off a reconciliation and a response back to the vendor. An invoice may enter a payment reminder cycle or may need matching against a rate card first. A contract gets filed and triggers nothing at all. Two messages carrying an identical document type can need entirely different workflows depending on who sent them and what stage the relationship is at.

Who or what sent it. Vendor, customer, or machine. System notifications look superficially like business mail — they have subjects, bodies, sometimes attachments — and should almost never enter a human queue. Getting this axis wrong is how a monitoring alert ends up assigned to an ops person as a customer query.

Predicting all three separately rather than one fused label keeps the failure modes independent. Getting the document type right and the workflow wrong is recoverable and visible. Fusing them into a single class means one misclassification takes out both, and you lose the ability to say which part of the judgement failed.

It also means you can be honest about confidence per axis. The model is often certain what a document is and genuinely uncertain what to do with it, and a fused label can't express that.

## The labels come from the filesystem

We keep training examples as ordinary `.eml` files in a directory tree, where the folder path is the label. Weight files sit under weight files. Invoices under invoices.

This is unglamorous and it's the best decision in the pipeline.

Labels live in version control, so a change to what counts as an invoice arrives as a reviewable diff with a name on it. Adding an example is dropping a file in a folder. Anyone in operations can read the corpus with a file browser and no query tool, which means the people who actually know what these documents are can inspect and correct the ground truth without involving an engineer.

And when the classifier gets something wrong in production, the fix is to add the offending message to the tree. Not to argue with a prompt, not to add a special case, not to tune a threshold. The correction mechanism is the same shape as the training data, which is the property that makes the thing maintainable over years rather than months.

The examples get compiled into optimised few-shot prompts offline, at build time, rather than assembled per request. Runtime is then a single call against a fixed prompt. Cheaper, faster, and identical between two invocations — which matters a great deal the first time somebody asks why the same email routed differently on Tuesday than it did on Monday.

There's a versioning consequence. The compiled prompt is a build artifact, so it has a version, and a routing decision can be attributed to a specific compilation. Without that, every behaviour change is unattributable and every investigation starts from zero.

## The routing is the hard part

Here's what nobody warns you about. Classification accuracy stops being the bottleneck fairly quickly. Routing is where the architecture gets tested.

The naive version hardcodes destinations. Weight file goes to service A, invoice to service B, in a conditional block inside the router. It works. It works right up until a fourth consuming service wants to handle a new intent — and now adding one intent means changing, testing and deploying the router, which has quietly become a component that every team's roadmap runs through and every team has to queue for.

The version that scales inverts it. Consuming services register the intents they handle along with where to deliver them. The router holds no knowledge of who consumes what. It classifies, looks up the registry, delivers. Adding an intent is a registration call from the service that wants it, not a deploy of shared infrastructure by a team that doesn't care about it.

Two things worth flagging, having built it the second way and got both of them wrong first.

Register the absolute destination or don't register at all. An early version accepted a relative delivery path and cheerfully stored it. The result was a registration that looked completely healthy in the table and pointed at nothing callable. The registration client now refuses to register when it can't construct a full address from what it's been given. Failing loudly at registration time is enormously cheaper than a queue that silently delivers into the void, because the queue has no reason to complain — it's doing exactly what it was told.

Seed the baseline on every deploy. An empty registry doesn't error. It routes nothing. And routing nothing looks precisely like a quiet morning, right up until somebody asks where Thursday's weight files went. Baseline intents are now seeded idempotently as part of deployment, so the table is never empty after a cutover or a fresh environment. This is a boring class of bug and it is exceptionally good at hiding.

Registration failures should also be observable. Ours is deliberately non-fatal, because a registry hiccup shouldn't take down a workflow that's otherwise fine. But the response carries whether registration succeeded, so non-fatal doesn't quietly become invisible. Those are different things and the difference is a few lines of code.

## What the model isn't doing

Worth saying plainly, because there's a lot of noise about mail agents at the moment.

The model is classifying and routing. It is not deciding anything commercial. It doesn't approve an invoice, doesn't accept a rate, doesn't answer a customer.

And the payload is almost always an attachment. The email body wrapped around it is frequently a signature block and the word "attached." Most of the real work lives in the document, and the mailbox layer's job is to identify what arrived, decide which pipeline it belongs in, and hand it over intact with enough context that the next stage doesn't have to re-derive anything.

That's a smaller job than the demos suggest. It's also the one that has to be dull and reliable before anything more ambitious sitting on top of it is worth building at all.

---

Blog index: https://www.rsaxb.com/blog
Site context for agents: https://www.rsaxb.com/llms.txt
