The Monday morning after Black Friday last year, my phone lit up at 7 a.m. A client's CEO texted me: "Lao Mi, our customer service team is getting roasted online."
A skincare brand, fulfilling out of a 120,000-square-foot 3PL warehouse in Ontario, California. All through Black Friday their homepage screamed "2-Day Delivery Sitewide." By Monday morning, over a thousand orders were still sitting in "awaiting shipment," the phones were melting down, and social media was calling them scammers.
I spent that afternoon in their office tearing apart the delivery-promise logic in their OMS. The warehouse wasn't slow — the pickers had worked past midnight all week. The real problem was that the system should never have made those promises in the first place. Available-to-promise (ATP) and delivery-date promising is the OMS feature most teams configure once and forget. It's also the one that embarrasses you the loudest.
Three traps. Let me walk through each.
The Inventory on the Books Isn't in the Building
Trap number one is the classic: the ATP math is wrong.
Available-to-promise sounds simple — if you have it, you can sell it. But what does "have it" actually mean? I've seen too many systems running a formula like: on-hand minus allocated equals sellable. Sounds reasonable. It's wrong, because on-hand quietly includes three things you cannot touch: inbound stock still in transit, soft-reserved stock, and QA-frozen stock.
In-transit inventory causes the most damage. One small-appliance client counted purchase orders "already shipped, on the way" as sellable, on the theory that "it'll be here in three days anyway." The week before Black Friday, a shipment of 8,000 humidifiers sat waiting for a berth outside the Port of Los Angeles for nine straight days. The website kept selling the whole time — over 3,000 units, every one of them promised "3-day delivery." By the time the container hit the dock, the CS team had apologized itself hoarse.
Reserved stock is the second landmine. Big B2B customer orders, replenishment plans for Amazon FBA — they tie up inventory in the system but haven't physically shipped. Some OMS platforms track reservations only at the order header level, not the line level, or they reserve without deducting from ATP. The worst case I ever saw: the same batch was reserved once by a B2B order and again by an FBA replenishment plan, and ATP deducted it zero times. One batch of inventory, sold three times over.
QA holds are the sneakiest. An inbound lot fails spot inspection, the whole batch gets frozen pending a supplier response, and it's locked in the WMS — untouchable. But if the OMS only reads the total on-hand number, it never sees the frozen status. At that skincare warehouse in Ontario, 20,000 bottles of serum got locked by QA over a labeling error right before Black Friday. The system kept selling them like nothing happened.
How do you check? My low-tech method: pull ATP numbers for twenty fast-moving SKUs out of the OMS, then have the warehouse physically count "what can we pick right now" for those same SKUs. Wherever the two numbers diverge, the gap is one of those three things. When fixing it, don't try to boil the ocean — change the formula to on-hand minus reservations minus frozen holds, and show in-transit stock as its own line, "expected arrival," broken out by arrival date. Nothing enters sellable quantity until it's physically in the building. I walked an 80,000-square-foot warehouse through that change in two weeks. Over-selling complaints went to zero after that.
The Promise Assumes Warehouse A. The Parcel Ships from Warehouse B.
Trap two lives in multi-node fulfillment. Single-warehouse math is easy: ship today plus carrier transit time. Add more warehouses and it gets messy fast.
When an OMS promises a delivery date, it has to answer two questions: which warehouse ships it, and how many days will it take? Plenty of systems compute a pretty delivery date for the storefront using "the warehouse closest to the customer," then, after the order is placed, the allocation engine re-selects the node based on "who actually has stock at the lowest cost." The storefront promised warehouse A's two days; the parcel actually leaves warehouse B and takes four. The customer watches the tracking page, sees "shipped from California" while they live in New York, and that's where the angry phone call comes from.

There's a subtler one: carrier cutoff times. The system calculates the promise assuming "ships today," but doesn't notice it's already 4 p.m. and the warehouse's daily cutoff was 3 p.m. That order really ships tomorrow — the promise was short by a day from the start. During peak season warehouses move cutoffs earlier, 3 p.m. becomes 2 p.m., nobody updates the parameter in the system, and every promised date runs a day late for weeks.
The fix runs in the opposite direction: make the promising logic and the allocation logic use the same rules. Before the storefront shows a delivery date, run the real allocation pass, find out which warehouse will actually ship it, then compute carrier transit from that node plus the cutoff check. Sounds obvious. I'd bet half the mid-market OMS platforms out there run promising and allocation as two separate code paths that never talk to each other. It's the demo I always demand during system selection: place three test orders to different ZIP codes and check whether the promised date matches the actual allocation. If they don't line up, that vendor is out.
Peak Season Arrives, and Every Promise Collapses
Trap three belongs to peak season. ATP looks accurate all year, then falls apart in November. The formula isn't the problem — concurrency is.
On a normal day, dozens of orders a minute, inventory decrements keep up. At midnight on Black Friday, thousands of orders a minute pour in, and ATP is still selling off a snapshot computed minutes ago. The snapshot says 500 units; in reality they were gone in three seconds. Everyone who ordered in the next two minutes still got promised "in stock, 2-day delivery." The warehouse shows up the next morning, there's nothing to pick, and the only options are apologizing one by one or waiting on replenishment while "2 days" stretches into "2 weeks."
Then there's the presale-versus-instock double sell. Marketing runs a livestream presale for "the batch arriving next week," while the website keeps selling current stock. The two channels don't share one inventory pool, so the same inbound batch gets promised twice. When it lands, there's enough for half the buyers. The other half's "2-day delivery" becomes "we'll notify you."
Peak-season fixes come in three parts. First, give promotional SKUs their own inventory pool, physically separated from everyday selling stock — presale is presale, instock is instock, and they never borrow from each other. Second, put pessimistic locking on ATP during high-concurrency windows, or at minimum push snapshot refreshes down to seconds — that one needs engineering involved, operations can't do it alone. Third, run a "promise stress test" before peak: replay last year's peak order volume against a test environment and watch when ATP starts lying. Knowing where it breaks ahead of time is a lot cheaper than apologizing afterward.
Don't Rush to Replace the System. Check in This Order.
A lot of owners want to rip out the OMS the moment this blows up. My advice: diagnose first, then decide, because nine times out of ten these traps are configuration problems, not platform problems.
Check in this order. First, do the twenty-SKU physical reconciliation and confirm whether the ATP formula deducts reservations and frozen holds correctly. Second, place test orders to different ZIP codes and compare the storefront's promised date against actual allocation and cutoff times. Third, replay your last peak season and sort the oversold orders: how many came from stale concurrency snapshots, how many from presale and instock sharing one pool. By the end of those three steps, you'll know the root cause.
Fix in priority order too. Correcting the formula comes first — no code changes needed. Unifying promising and allocation rules comes second — that needs engineering time. Concurrency locking and pool separation come third — schedule it a month before your next peak. In that sequence, a warehouse doing three thousand orders a day can get delivery accuracy back to normal within a quarter.
One last question for you: the delivery-promise number in your system — when was the last time someone walked the warehouse floor with the ATP report and reconciled it SKU by SKU? If you can't remember, the next broken promise is probably already on its way.







