WritingCommerce
Ship each item as it lands, hold for all, or hold until a deadline
Pick a parcel consolidation policy for multi-seller cross-border orders, fix it on the order at checkout, and release on a deadline rather than on hope.
On 3 October a customer in Dhaka places one order for three items from three sellers: a £38 phone case, a pair of trainers at £120 and a £9 USB cable. All three are addressed to your hub in New Jersey, and from there they fly to Bangladesh. The case is scanned in on 6 October and the trainers on 9 October. The cable's seller marked it dispatched on 4 October and the tracking page still says "label created". On 6 October the hub is holding one small box and has to decide whether to ship it now, wait for the other two, or wait until some date and then send whatever has turned up.
Anyone running a forwarding hub meets this on every order with more than one inbound parcel, and the choice is usually made badly because it is made in the warehouse, box by box, by whoever is holding the scanner. I have built cross-border procurement across Amazon, Alibaba, AliExpress and 1688, and the rule I apply is that the consolidation policy is a column on the order, chosen and priced at checkout before the first item lands. The warehouse executes the policy it is given; it never picks one. The counter-argument is that the warehouse knows more on the day than checkout did, and the rest of this post is the case against it.
What decides it
There are three policies worth building. Ship as it lands forwards every inbound parcel the day it is received. Hold for all keeps everything at the hub until every expected item has been scanned in, then sends one parcel. Hold until a deadline keeps items until either everything has landed or a release_by date fixed at checkout passes, then ships what is there and refunds what is not. They differ on things a checkout page, a hub and a finance team can each measure.
| Criterion | Ship as it lands | Hold for all | Hold until a deadline |
|---|---|---|---|
| International legs per order | One per item | One | One, or two if an item lands after release |
| Time to first delivery | Days after the first item lands | Days after the last item lands | Bounded by the deadline |
| Customs declarations | One per parcel, each at its own value | One at the combined value | Usually one |
| Exposure to a missing item | None, the others have already gone | Unbounded, the order never completes | Bounded by the deadline |
| Hub storage per order | Hours | Until the slowest seller delivers | At most the hold window |
| What checkout must show | Three shipping charges | One charge and no ship date | One charge and a latest ship date |
| Refund when an item never ships | The item only, whenever you notice | The item, then the rest ships late | The item, at release, automatically |
The customs row needs one caution. Where a destination exempts low-value consignments from duty, splitting an order into three small parcels and combining it into one can land on different sides of the threshold. Treat that as a rule to check per destination, not a lever to pull: some authorities treat a deliberately split consignment as one, and the policy that saves duty on paper can end with the parcel held at the border and the duty billed anyway.
Ship as it lands
Best for Single-seller orders and anything the customer needs by a date
Each parcel is forwarded within a day of receipt with its own label, its own declaration and its own tracking number.
- strengths: the customer gets the first item fastest; the hub holds no state, so there is nothing to sweep or expire; a seller that never ships costs one refund and nothing else
- costs: three international legs for three items, each carrying the carrier's minimum charge; three declarations to prepare; the customer tracks three parcels and writes to support about the slow one
Hold for all
Best for Orders where every seller is one you have measured and trust to dispatch
Nothing leaves the hub until every expected item has been scanned in, and then one parcel goes out.
- strengths: the lowest shipping cost per order; one declaration and one tracking number; the simplest promise to word on the checkout page
- costs: one seller who cancels on day 12 holds the other items hostage; storage per order has no upper bound; there is no honest ship date to show the customer, because you do not know one
Hold until a deadline
Best for Multi-seller orders where cost matters more than speed
Items are held until all have landed or release_by passes; at release the hub ships what is there and the missing items are refunded without anyone deciding to.
- strengths: one parcel in the common case; the customer sees the saving and the latest ship date next to each other; a missing item is contained by the clock rather than by a support ticket
- costs: a release job that has to run on time and refund correctly; a second leg when an item lands the day after release; a window that has to be chosen per lane and defended when a customer asks why it is nine days
flowchart TD
A[New order at checkout] --> B{More than one seller}
B -- no --> S[Ship as it lands]
B -- yes --> C{Customer paid for speed}
C -- yes --> S
C -- no --> D{Every seller measured reliable}
D -- yes --> H[Hold for all]
D -- no --> R[Hold until a deadline]:::accent
S --> E[Store policy and release by]
H --> E
R --> EEvery question in that chart is answered from data the order already carries: the count of distinct sellers, the delivery speed the customer chose, and each seller's dispatch rate over its recent orders. None needs a person or a parcel that has arrived, which is why the policy can be fixed at checkout.
Where hold for all breaks
Take the Dhaka order under hold for all, with no deadline. On 6 October the case is scanned in and order_item 1 moves to received. On 9 October the trainers follow. The cable's item stays in expected, and the order stays in awaiting_items, which the customer's order page renders as "Preparing your parcel". On 12 October the marketplace cancels the cable because the seller never shipped; the cancellation arrives as a status on the marketplace order page, and nothing in the ingestion maps it onto the item row. The order still reads three expected, two received.
The customer writes to support on 14 October and again on 18 October. Support refunds the £9 by hand on 18 October, but the item is still expected, so the release check still fails and the two boxes stay on the shelf. Somebody eventually edits the row. The hub saw two parcels under one order number with no due date for twelve days; the customer saw "Preparing your parcel" for the same twelve days, then a late delivery of two items. What contains it is two things the hold for all policy does not force you to build: a terminal state per item, so a cancellation or a refund is a release trigger in its own right, and a date after which the order releases whatever its items say.
// simplified: decides whether the hub may ship what it holds right now
type ItemState = 'expected' | 'received' | 'cancelled' | 'lost';
type Policy = 'ship_as_it_lands' | 'hold_for_all' | 'hold_until_deadline';
interface Order {
policy: Policy;
releaseBy: Date | null;
items: { state: ItemState }[];
}
function shouldRelease(order: Order, now: Date): boolean {
const stillExpected = order.items.filter((i) => i.state === 'expected');
if (stillExpected.length === 0) return true; // landed, cancelled or lost
if (order.policy === 'ship_as_it_lands') return true;
if (order.policy === 'hold_for_all') return false;
return order.releaseBy !== null && now >= order.releaseBy;
}The release job runs this once an hour over every order in awaiting_items. An item that moves to cancelled or lost leaves stillExpected, so the refund and the release happen in the same pass, and a job an hour late is the worst case rather than a week of silence. Under hold until a deadline, releaseBy is set at checkout from the lane's measured arrival times: if the slowest seller on the order usually lands within six days, a nine-day window catches nearly every order and still bounds the ones it does not.
Whichever policy wins, build the same four things first: a consolidation_policy and a release_by column on the order, a state on every expected item with cancelled and lost as terminal values, an ingestion step that maps a marketplace cancellation onto the item, and an hourly release job that ships and refunds from those fields alone. Once those exist, the policy is a value the checkout writes, and changing your mind about the window is a config change rather than a conversation with the warehouse.