CompertMe

Case study 03 · Air cargo, customs and last-mile operations · 2025 – 2026

Eight months turning an air cargo handler's undocumented knowledge into a platform that enforces it

120 compliance rules, 40 status codes, and a contract signed on software that was seven days old.

Written by Hesham Ghoniem, lead engineer ·

Sector
Air cargo, cross-border e-commerce logistics, customs
Engagement
2025 to 2026 · specify, build, deploy
Scope
Full platform · 11 operational modules, partner API, public tracking site
Stack
PHP 8.2
Laravel 12
Vue 3
Inertia
Tailwind
MySQL
Redis
AWS S3
  • 7 days — Empty repository to a live, documented API. Both sides signed the contract on it
  • 120+ — Individual compliance rules encoded and enforced
  • 40 — Status codes governed, across 17 steps and two lifecycles
  • 200 — Bags on one flight, updated in a single action
  • ~30,000 — Lines of application code

Overview

There was no system to do it yet. We built the platform it needed.

An air cargo handler was about to take on a new partnership. Every parcel it handled would have to be reported to an international shipping partner, step by step from customs to the doorstep, in the partner's own codes and in the right order. There was no system to do that yet. And the rules governing which status could legally follow which, the thing the whole operation would run on, had never been written down.

We built the platform it needed. Eleven connected modules now carry a parcel from the moment a flight is announced to the moment revenue is invoiced. Shipments arrive by API. Customs bundles are produced in one click. Thousands of status updates go out as a single action. The platform converts and files duties as staff enter them, and prices revenue per package from a grid the client maintains themselves.

The engagement was urgent from the first conversation and never stopped being urgent. It began with a contract the client could not sign. Their partner would not countersign without a working, documented API to integrate against, and that is normally a month of work. We delivered it in seven days, deployed to production, and both sides signed the contract.

That pace held for eight months: fifty-eight active delivery days, a third of the work landing on weekends, and features shipping within days of being asked for. We chose to slow down in one place only, the rulebook. That decision is why the engine absorbed a year of changing regulations without a rewrite.

Where one status goes

An operator picks a status in the platform, which offers only the statuses the rulebook allows. The platform pushes it to the shipping partner in the partner's codes and inspects and records the reply, and the partner passes it on to the merchant and the customer within minutes. A wrong code comes back as a claim.

The challenges

Seven problems, and the hardest one had never been written down.

The contract was waiting on software that did not exist

The terms were agreed and the contract sat unsigned, because the shipping partner would not countersign until their engineers could see a real API. They wanted to authenticate, push a shipment, push packages, read a status back, and find the answers in documentation without emailing anyone. Not a mock. Not a slide deck.

There was no data model, no authentication, no infrastructure and no repository, and the deal had days left before it went cold.

A compliance rulebook nobody had ever written down

Ask the operations team which status comes next and you get the right answer instantly. Ask them why, and you get a pause. Forty status codes, dozens of ordering rules, timing constraints, manager-only exceptions. All of it correct, all of it undocumented, all of it held by the people who had done the job long enough to know.

A wrong status code is a commercial event, not a typo

This is what made challenge 2 urgent rather than untidy. Every status pushed to the shipping partner travels on to the merchant who sold the item and the customer waiting for it.

Miss the exception code on a failed delivery and the merchant's system never learns why, so the delivery clock keeps running against the handler. File a status out of sequence and the partner rejects it outright, so the parcel stops producing tracking events in the middle of its journey while everyone downstream assumes it is moving.

Mark a parcel delivered when it is not, and the merchant closes the order while the box is still on a shelf. Then the customer opens a claim.

One flight, thousands of parcels, one status each

A flight arrives as a single master air waybill holding as many as 200 bags, each holding many packages. When it clears customs, every package inside needs the same status pushed to the partner. By hand that is days of work. All at once, as fast as a server can send them, gets the integration throttled or cut off. And if the process dies halfway, nobody knows which parcels made it.

Customs paperwork scattered across the internet

Customs requires the commercial invoice for every package on the flight. Those invoices sit on a few hundred different shippers' servers, behind links that expire, redirect, block anything that does not look like a browser, or return an error page with a PDF content type. Assembling one flight's bundle by hand would mean hours of clicking and hoping nothing was missed.

Money moving in two currencies and two directions

Import duties are assessed locally and must convert to US dollars before being filed with the partner's customs system. Revenue runs the other way. The handler bills it per package, on a grid that varies by parcel weight and by monthly shipping volume, with surcharges for multi-item parcels and for packages carrying local tax, from parcels weighed in four different units.

A rounding difference on one parcel is noise. The same difference across a month's traffic is a hole in the books.

An external system that hides its failures

The request returns. The status code says 200. And three levels down in the response body sits an error the partner's system raised and nobody read. Left alone, the two systems drift apart, ours showing a duty filed and theirs showing nothing. Months later a dispute arrives, and "we think we sent it" is not an answer.

Day 1 to day 7 · the partner contract

The week that got the contract signed

The client's growth depended on one signature, and the signature depended on software that did not exist.

Their prospective shipping partner had a straightforward position. No integration, no contract. Before anything was countersigned, the partner's own engineering team wanted to open a browser, authenticate against a real endpoint, push a real shipment, push real packages, read a status back, and find the answers in documentation without asking a human.

A production API with documentation attached is a month of work under normal conditions. The client did not have a month.

We scoped the entire first week around one outcome that was not a feature. No dashboards, no reports, no admin screens, nothing the partner's engineers would never touch. Everything that was not blocking a signature waited.

Seven days, empty repository to production

A single line across seven days with five markers: day 1, an empty repository; day 2, authentication, tokens and rate limits; day 6 at 3:49 a.m., the package endpoints; day 7, the shipment endpoints and documentation; and day 7 again, production. A bar under the line reads: contract signed.

Security first, then eighteen endpoints in production.

On the evening of day one the repository was empty. By late on day two it had token authentication, revocable API keys, rate limiting on every endpoint and the first importable request collection. The security floor went in before a single feature.

At 3:49 a.m. on day six the package endpoints landed: create, read, update, search by status, track by number. On the evening of day seven the shipment endpoints and the full documentation site went in together. 18 endpoints, live, with a browser-based tester the partner's engineers could send real requests from and a pre-built request collection they could import. The same day, it was deployed to production, not a demo server.

We did not copy the validation rules into the documentation by hand, which is part of why it could ship that fast. The documentation reads them live from the same place the API uses to enforce them, so the rules on the page are always the rules in force.

And it set the pattern.

We built the rest of the platform at the same pace, for the same reason. The client needed every module as soon as we could build it. Fifty-eight active delivery days across eight months, and a third of all commits landing on a weekend.

Both sides signed the contract on it. Seven days from an empty repository.

— Hesham Ghoniem, lead engineer

What we built

Eight months, built in the order the business needed it.

Chapter one · 2025 · One week, production

The integration, before anything else

Authentication, revocable partner tokens, rate limiting, then the package and shipment endpoints and the documentation site that made them usable by someone else's engineers. One week, production, contract signed. We built every later piece of the platform on top of that.

Chapter two · late 2025 · The data model

The operation gets a shape

The data model the rest of the platform stands on: flights as master air waybills, bags beneath them, packages beneath those, items beneath those, and consignees with the tax identifiers customs demands. Then the screens staff work in all day, covering packages, bags, shipments, clients and package tracking.

Behind them sits the invoice collection, which fetches each parcel's commercial invoice when the package is registered, and keeps retrying any that fail on a schedule, rather than asking anyone to go looking later.

Chapter three · end of 2025 · Three modules in six weeks

The money

Three modules in six weeks. Import duties, entered in local currency and converted at a rate the client controls, filed to the partner's customs system and written to a permanent audit log. Shipment costs. Revenue, priced per package from a weight-and-volume grid the client now maintains in a settings screen without a developer. The revenue export puts local tax on its own line, separate from the shipping charge.

Chapter four · turn of 2026 · The centre of the platform

The rulebook goes in

The compliance engine, the parcel lock, and the full status timeline that records who changed what, when, and what the partner said back. This is the centre of the platform, and it has its own section below.

Chapter five · early 2026 · Same-day changes

The rules keep moving, and the platform keeps up

Four months of refinement alongside the operations team as real parcels were walked through the engine. Mappings corrected. A code moved between steps after a customs process changed. A prohibited-items status added with manager-only access and an automatic freeze. An unclaimed ending for parcels nobody collects. A lost-in-transit status across both delivery-result steps.

Every one of those shipped the same day it was agreed, and not one required touching the engine.

Throughout

The things that never move

The platform logs every call in and out in full: payload, response, timing, and the staff member behind it. To keep that from overwhelming the database, the log row holds only the summary while a background worker writes the bulky content to cloud storage. The audit trail is complete and the operator never waits for it.

A public tracking site runs on its own web address, served by a separate data path that cannot reach operational data, so a customer can check their own parcel without calling anyone.

Technical deep-dive · the status engine

Inside the rulebook: the status engine

A parcel status looks like a small thing. One code, entered by one person, in one field. It is in fact the riskiest input in the platform, for the reasons in challenge 3. A wrong code reaches merchants and customers within minutes and comes back as a claim, a penalty, or a parcel with no tracking events.

This was never a data-entry problem

A commercial risk shaped like a dropdown.

Step one was getting the rules out of people's heads, and it was the hard part.

There was no document to work from. There was a list of status codes with no ordering, a partner specification describing what each code meant but not when it could be used, and the operations team, who knew. So we sat with them and asked the same question in different shapes, over and over. What can follow this? What if the courier tries twice? What if the customer collects it themselves? Who is allowed to mark something damaged, and can anyone touch it afterwards? If a parcel goes back out for a second run, is that the same flow or a different one?

The answers came back fast and confident, and then contradicted each other two questions later. Nobody was wrong. Nobody had ever needed the edge cases to be explicit, because staff had always handled the exceptions by judgement. Software has no judgement. Every "well, it depends" had to become a rule with a condition attached, and finding the condition took a lot of back-and-forth with the client.

We wrote what we heard into a plain-English specification, with no notation, no jargon, and no diagrams anyone would have to be taught to read. We walked the operations team through it line by line and revised it until they signed it off. It reads step by step. At this point these statuses are available. This one needs a manager. This one ends the flow. This one sends you back to the beginning. This one must carry a date matching the one before it.

We refused to write a line of the engine until the client approved that document. It cost a week at a moment when the client had no weeks to spare. It was the best week we spent on the project.

What it turned out to be

  • 40 Status codes — The whole vocabulary, in the partner's own codes.
  • 17 Ordered steps — Across 2 distinct lifecycles: a first pass, and a different flow for every re-delivery run.
  • 120+ Individual rules — Counted, not estimated: 62 status placements plus 60 behavioural rules, all in one configuration file.
What the 120+ is made of

The rules, by kind and drawn to scale: 62 valid status placements, 19 manager-only restrictions, 19 auto-freeze rules, 7 date, time and business-day constraints, 7 terminal endings, 4 skip rules, 2 jump rules and 2 restart points. 122 in all.

122 in all, which is where the 120+ comes from.

A parcel does not march through those steps in a straight line.

Clear customs cleanly at step two and step three disappears, because there is nothing to clear twice. Hand the parcel to a customer who collects it themselves and the engine skips the delivery-result step. Deliver it to a local office and the entire cycle restarts from customs entry, but the second cycle carries different exception codes than the first, because a re-delivery run fails for different reasons than a first attempt. Mark a parcel prohibited, damaged or not received and it freezes on the spot. Only a manager can touch it again.

The timing rules catch humans out most often. A delivery result must fall on the same calendar date as the attempt that triggered it, and at a later time than that attempt. The same pairing applies again to the second attempt and its result. A ready-for-collection notice must land within two business days of the failed delivery, and because they are business days, the engine has to know about weekends.

Two cycles, and a path that is not a straight line

Two tracks of numbered steps. The first cycle runs nine steps from customs entry to final resolution. The second, for re-delivery runs, is shorter at eight steps and carries different exception codes at its delivery-result step. A curved arrow runs from the first cycle's final resolution back to the second cycle's customs entry: delivered to office, cycle restarts. Dashed steps can be skipped depending on what happened earlier, padlocked steps hold manager-only statuses that freeze the parcel, and clocked steps carry timing rules.

How all of that reaches the person doing the work

It resolves into an interface with almost nothing in it.

An operator opens a parcel and sees a dropdown containing only the statuses legally available to that parcel, at that point, for that user. The invalid ones are not greyed out. They are never rendered at all. A regular operator never sees a manager-only code, so they never ask why it is disabled. A delivered parcel offers nothing. A frozen one offers nothing.

Where a status carries a timing rule, the date field appears with the rule written out in plain language, filled in from the parcel's own history.

The same parcel, the same moment, two dropdowns

On the left, a naive dropdown lists all forty status codes with most of them greyed out: what most systems do. On the right, the real dropdown holds three options: what an operator sees. Under it, the date field states its rule in plain English, filled in from the parcel's own history: must be the same date as the delivery attempt, March 14, 2026, at a time after 11:00 PM.

If the operator gets it wrong anyway, the error names what the rule expected and what the operator submitted.

It replays the whole history, every time.

The engine works out where a parcel stands by replaying its whole history from the beginning, every time, rather than storing a current step that could drift out of sync with reality. That one decision meant the engine read parcels already mid-journey correctly on day one, including ones carrying older status codes from before the rules existed, which it translates as it reads. Not a single live record had to be migrated.

Why it survives being wrong

The client approved the rulebook before we wrote the engine, and kept revising it after it went live. We designed for that.

None of the rules live in code. The steps, the allowed codes, the manager restrictions, the freeze behaviour, the timing constraints, the skips and the jumps all sit in a configuration file the engine reads. Adding a status code is a few lines. Moving a code between steps is a cut and paste. Four separate partner-driven changes arrived between February and May 2026, and each one shipped the same day it was agreed.

One commit in three on this project touches the status system, 41 of 125. The engine was not built wrong. It models a business whose rules change.

— Hesham Ghoniem, lead engineer

How we work

Eight rules, and one place we slowed down.

Find what is blocking the business, and do that first

On this project the answer was not a feature. It was a signature. We scoped the whole opening week around what one group of external engineers needed to see, and everything else waited its turn.

Match the client's urgency for the whole engagement, not the first week

Anyone can sprint once. This client was assembling a platform while their operation already ran on it, so the work stayed continuous. Fifty-eight active delivery days, a third of commits on weekends, turnaround measured in days.

Write the specification in the client's language, not ours

No notation, no diagrams they would have to be taught to read. If the people who hold the knowledge cannot check the document, it has not captured the knowledge.

Settings, not releases

Status codes, transition rules, timing constraints, pricing tiers and exchange rates are all configuration, because none of them belong to us. They belong to partners and regulators who change their minds.

Never break what is already in flight

Real parcels were mid-journey for the entire build. We wrote the engine to interpret inconsistent historical data rather than demand it be cleaned up first, so every feature launched with no maintenance window, no migration weekend, and no parcel left in an unknown state.

Assume the system you depend on will lie to you

The partner's API can return success and hide a rejection in the body. The platform inspects every reply, and a rejection rolls the local operation back, so it never records something the partner never accepted.

Anything slow, external or rate-limited runs in the background

Invoice retries, bulk updates, audit logging and partner calls never block a person. The interface stays fast because the waiting happens where the operator cannot feel it.

Slow down in one place only, on the part that corrupts everything downstream

Speed is the right instinct for most work and the wrong instinct for a compliance rulebook. We spent a week writing it in plain English and getting it signed off, which is why the engine absorbed a year of changes without a rewrite.

Technologies used

Nine layers, and what each one is for.

Application

Mature, long-supported, with a very large hiring pool. The client is not forced into a rewrite in three years.

  • PHP 8.2
  • Laravel 12

Interface

The speed of a single-page app without the cost of maintaining a separate front-end application. Large tables expand on demand and cache what they have loaded, so operators never wait on the same data twice.

  • Vue 3
  • Inertia 2
  • Tailwind
  • Vite
  • Pinia

Compliance engine

We change the business rules by editing a file, not by rewriting code. Four partner-driven changes shipped same-day in 2026 without touching the engine's code.

  • Configuration-driven state machine
  • 120+ rules in one settings file

Data

The full parcel lifecycle: flights, bags, packages, items, consignees, timelines, duties, pricing and audit logs.

  • MySQL
  • 27 tables
  • 46 schema changes

Background work

Bulk updates, invoice retries and audit logging run off-screen, with a live dashboard showing what is processing and what failed.

  • Redis
  • Laravel Horizon
  • Queued jobs

Partner integration

The partner's engineers could send real requests to every endpoint on day one. The page reads its validation rules live from the API itself, so they always match what it enforces.

  • Laravel Sanctum
  • Revocable tokens
  • Tiered rate limits
  • Generated documentation
  • In-browser tester

Documents and storage

Commercial invoices, customs manifests and billing exports. Private storage, expiring links, cached bundles.

  • AWS S3
  • ZIP bundling
  • Signed temporary links

Access control

Manager-only statuses, parcel lock and unlock, and API token issuance restricted to administrators.

  • Jetstream
  • Fortify
  • Two-factor
  • Email verification
  • Role-based permissions

Monitoring

Every partner call recorded with payload, response, timing and the user behind it.

  • Laravel Pulse
  • Full inbound and outbound request logging

Automated testing

Automated tests check each change before it reaches production.

How the platform works

Seven steps, from a flight posted by API to a parcel the customer can track.

Step 1

Receive

The shipping partner posts a flight straight into the platform: the master air waybill, every bag inside it, and the packages in each bag. One atomic operation creates and links the records, so a shipment lands complete or not at all, and the platform refuses duplicate package or item references at the door.

Step 2

Enrich

Every parcel gets a full profile covering consignee, address, tax identifier, contents, declared values, weights and dimensions. The platform fetches its commercial invoice from the shipper's own server when the package is registered and files it in private storage. A scheduled job retries any fetch that fails.

Step 3

Clear

The platform produces the full customs manifest and a complete invoice archive for the flight, organised bag by bag, in one click, then caches it so the second request is instant.

Step 4

Move, inside the rules

Staff update parcel statuses from the screen, choosing only from what the rulebook permits at that point for that user. Every change is timestamped and attributed, then pushed to the partner's tracking system, with the partner's reply recorded against it.

Step 5

Move in bulk

When something applies to a whole flight, one action queues the lot. A background worker paces itself so the partner is never overwhelmed, writes progress to the database after every parcel, and lists anything skipped by name with the reason. Only one batch runs per shipment at a time.

Step 6

Account

Import duties convert to dollars and file to the partner's customs system, with each submission verified and logged and each change written to a permanent audit trail. The platform prices revenue per parcel from the client's own grid, with local tax split onto its own billing line. Both roll up per flight, ready to export.

Step 7

Publish

A public tracking site on its own web address lets customers look up a parcel and follow its journey, served by a separate data path that returns only what is safe to show.

Step 1, in detail: what one flight holds

A flight, recorded as a master air waybill, holds up to 200 bags. Each bag holds many packages, and each package holds its items. The consignee, with the tax identifier customs demands, hangs off the package.

One atomic operation creates and links the whole flight, so a shipment lands complete or not at all.

Results

What the business can do now that it could not in September 2025.

Sign contracts on working software

A live, documented, production API with 18 endpoints, token authentication, rate limiting and a browser-based tester, delivered in seven days and used to close the deal the business was waiting on.

Run a 120-rule compliance flow without a specialist in the room

The platform enforces 40 status codes, 17 steps, two lifecycles and every timing and permission restriction, instead of a person remembering them.

Stop sending bad data downstream

Out-of-sequence updates, false delivery events and missing exception codes, the errors that reach merchants and customers as claims and penalties, are now impossible to send.

Update a whole flight in one action

Thousands of parcels dispatched as a single paced background job with live progress and a per-parcel skip report, instead of days at a keyboard.

Hand customs a complete bundle on demand

The full manifest and every commercial invoice for a flight, organised by bag, in one click.

Know what went out and what came back

Every status change carries the user, the timestamp, the reported event date and the partner's response. The platform logs every duty adjustment permanently. A rejected submission rolls back instead of letting the two systems disagree.

Change its own commercial rules

The client edits pricing tiers, exchange rates and volume bands in the application, with no developer and no release.

Absorb a regulator or partner changing their mind

Four status-flow changes were taken on in 2026, each live the same day it was agreed, none requiring a code change to the engine.

Let customers answer their own question

A public tracking site lets a customer look up their own parcel without calling anyone.

Bill from the system that did the work

The platform prices revenue per parcel as the work happens and exports it per shipment, with local tax on its own line.

The engineering, in numbers

  • 120+ Individual compliance rules encoded
  • 40 Status codes governed
  • 17 Ordered steps across two lifecycles
  • 62 Valid status placements
  • 19 / 19 Manager-only restrictions / auto-freeze rules
  • 7 Date, time and business-day constraints
  • 41 of 125 Commits touching the compliance engine
  • 18 Documented partner API endpoints
  • 11 Operational modules
  • 27 Kinds of record the system stores
  • 46 Database changes applied without interrupting service
  • 100 Vue screens and components
  • 3 / 14 Background job types / maintenance commands
  • 200 Bags per flight supported in one batch
  • 58 Active delivery days across the engagement
  • ~30,000 Lines of application code

Honest status

The last commit is dated 20 May 2026, so this page describes our engagement in the past tense.

No business metrics have been verified. Every number on this page is counted from the repository, and nothing here claims hours saved, parcel volumes, error rates or uptime.

Related work

Cross-Border Forwarding Platform

Logistics · 2018 – present · Build, run, maintain

8 years — Continuous development, one data-model rebuild, zero downtime

All work

Next step

Does your next contract depend on software that doesn't exist yet?

Work with an engineering team that finds what is blocking the business and builds that first, then slows down in one place only: the rules everything else depends on.

Book a discovery call

Client anonymised · every figure counted from the repository and its 125 commits · October 2026