Most shipping automation only has to handle one path: order comes in, label gets bought, done. That’s an easy demo. It’s also not what a real online store selling physical products into a dozen countries looks like, whatever ecommerce platform it’s built on (Shopify or WooCommerce, for example, though the logic here holds regardless of which one).
I recently rebuilt the order-to-shipment pipeline for a client shipping physical products worldwide across multiple product lines. The old version was two separate automations, built in whatever no-code tool was easiest at the time (Zapier is the usual suspect, though Make shows up just as often), stitched together by habit rather than design, and undocumented enough that nobody fully understood how they worked. The new version is a single workflow, built in a platform suited to this kind of branching, multi-step logic (n8n is what I reach for most, Make is the other common choice), that checks an order is safe to ship, works out which region and courier it belongs to, buys the label, and then goes back and checks its own work. That last part is the bit almost nobody builds, and it’s the bit that actually matters.
Here’s how it’s put together, and why.
The gate list: everything that can stop an order before it ships
Every order runs through the same checks before anything touches a courier. Each one is a gate: fail it, and the run stops there and gets logged, rather than limping on with bad data.
- Duplicate check. Query the shipping platform for an existing shipment on this order number first. If one exists, stop. Without this, any retry or re-run risks buying a second label for the same parcel.
- Fraud risk check. Most ecommerce platforms score order risk asynchronously, so a brand new order often comes back “pending” rather than a real answer. Rather than treat pending as safe, the workflow waits and rechecks, up to four times over an hour, before giving up and flagging a human. Medium or high risk goes straight to a person, no retry loop, because that’s not a timing problem, it’s a decision.
- Channel check. In-person point-of-sale orders don’t go through this flow at all.
- PO box detection. Not every courier delivers to PO boxes, so the address gets pattern-matched against PO box formats in English and half a dozen European variants, catching things like “Postfach” and “Boîte Postale” as well as the obvious “PO Box 12.”
- Address validation. Run against the shipping platform’s own validation before anything is created.
- Weight calculation, from line item quantities, because the courier needs a number and “roughly the same as last time” isn’t one.
Every one of those failures writes to a log with the order number, the stage it failed at, and what’s known so far. Nothing fails silently. If a customer’s parcel doesn’t ship, there’s a specific line in a specific log that says why, not a shrug and a support ticket three days later.
Routing by region, with a fallback plan for when the first courier says no
Once an order clears the gates, it gets sorted by destination, with a different courier setup per region:
- Standard shipments in one region wait a couple of hours (so same-day cancellations have a window) before going to the primary courier. Express orders try one courier first and automatically fall back to a second if the first is unavailable (this kind of primary-plus-fallback pairing is common with couriers like Royal Mail, DPD, DHL or FedEx), with an alert either way so someone knows a fallback was used.
- Some regions split further still, by country or destination type. The exact split matters less here than the principle: a single “EU courier” or “rest-of-world courier” setting rarely reflects how delivery actually works once you look closely at any one region.
Every create, update, and label-purchase step in every branch has a paired check: did that call actually succeed? If not, it logs to a failure log with the destination, the stage, and the shipping method, so the first place to look when something’s gone wrong is one place, not raw API logs.
The part everyone skips: does “label bought” actually mean “order shipped”?
Here’s the uncomfortable finding that shaped the whole rebuild. Buying a shipping label does not reliably flip an order’s status to fulfilled in the ecommerce platform. They’re two separate systems, connected by an API call that can fail, time out, or just not fire, and nothing about “the label exists” guarantees “the store’s system knows about it.”
In the previous setup, that gap was covered by a second automation nobody had documented. When the rebuild first went live, that second piece wasn’t replicated, because nobody knew it needed to be. The result: a real batch of orders shipped correctly, with real labels and real tracking, while sitting in the system as unfulfilled. Customers had their parcels. The store’s own systems didn’t know that.
That’s not a coding mistake. It’s a systems mistake: treating “call the API” as the same thing as “confirm the outcome.” The fix isn’t a patch, it’s a structural change: every branch that successfully buys a label now feeds into one shared verification chain. Wait a few hours, look the order back up, and if it’s still not marked fulfilled, create the fulfilment directly rather than assuming it’ll sort itself out. Already-fulfilled and order-not-found both stop quietly, because those aren’t problems. Anything that still fails after that gets logged to its own place, because at that point it needs a person.
Building it inline, fed from every branch, rather than as a separate workflow someone has to remember exists, is the actual fix. The old failure mode was structural. The new design closes that specific door.
The lesson, if you’re building your own
If you’re automating fulfilment and your workflow diagram ends at “buy label,” you’ve built half of it. The other half is confirming the label actually did what you assumed it did, and having somewhere for the failures to land that isn’t a support inbox three weeks later.
This is the kind of gap that only shows up once real volume goes through a system, which is exactly why it’s worth building the verification and logging in from day one rather than bolting it on after the first incident. It’s cheaper before it happens than after.
If this is the kind of gap sitting in your own fulfilment or support stack, right now, and you don’t know it yet, that’s normal. Most automation built under time pressure has one somewhere. This build was one phase of a longer rollout for this client. If you want to see where it fit in the full sequence, that’s worth a read too. I walk through exactly this kind of sequencing (what to check first, what can wait, how to build in verification rather than assuming success) in The Automation Roadmap, my Maven workshop on building your team’s first 90-day automation plan. Details and dates are here.