Delivery promise¶
Checkout says when an order will arrive, worked out from where it is sent: "Arrives Tuesday 13 October", or "Arrives Thu 15 Oct - Mon 19 Oct" for a range, and "Order within 1 h 13 min for delivery tomorrow" while today's cut-off is close. The product page and basket say how soon to order for the fastest delivery, and the confirmation email, the order page and the admin order view show the date the customer was promised.
It is on for every plan as soon as a delivery option has a promise set. An option without one makes no promise: checkout shows its own description instead, never a made-up estimate (the old hard-coded "3-5 business days" has gone).
How a promise is worked out¶
order placed ──► pick & pack ──► dispatch cut-off ──► courier collection ──► transit (working days) ──► delivered
(location) (location/option) (location) (option: min..max)
- Where it is sent from. The order is sourced first, with the same plan that will take its stock (see Locations), so the promise is from the location that will send it. A shop with one stock location (or an order nothing could be planned for) is promised from its main location. An order split between locations is promised from the one that sends last.
- The dispatch day is the first day, from today in that location's time zone, that:
- has a courier collection that weekday (the location's Courier collection; Monday to Friday at 17:00 when it has never been set);
- isn't closed by its opening or special hours, or inside a temporary closure;
- isn't one of the shop's non-working days, unless the location's special hours open it that day;
- with Store capacity limits on (Enterprise), still has ship-from-store capacity left;
- and, for today, is before the cut-off.
- The cut-off is the last collection less the pick and pack time (60 minutes unless set), and never later than the location's closing time that day. A delivery option can have its own cut-off ("order by 3pm"), which replaces the location's.
- Delivery is the dispatch day plus the option's transit: 1 working day is next day; 2 to 4 is a range. Transit days are days the carrier delivers (Monday to Friday unless the option says otherwise, e.g. Saturdays too) that aren't one of the shop's non-working days. 0 is same day.
Nothing is looked further ahead than three weeks: a location closed until further notice makes no promise.
The shop's non-working days¶
Settings › Shipping and labels › Delivery dates:
| Setting | Key | Default |
|---|---|---|
| Bank holidays | delivery-holiday-region |
england-and-wales; or scotland, northern-ireland, none |
| Other non-working days | delivery-closed-dates |
none: one per line, 2026-12-24 or a range 2026-12-24..2027-01-01, optionally with a note |
The bank holidays are bundled with the platform from GOV.UK's published dates
(commerce-core/src/main/resources/delivery/uk-bank-holidays.csv, currently 2026 to 2028). The
file needs refreshing every year from https://www.gov.uk/bank-holidays.json (and whenever a
one-off holiday is announced); dates after the last year in it count as working days. Christmas Day
and Boxing Day count as non-working in every UK region whatever day they fall on, since GOV.UK only
lists the substitute weekday when they fall at a weekend. A date that doesn't read is refused when
the settings are saved.
What the shopper sees¶
| Where | What |
|---|---|
| Checkout, under each option | "Arrives Tuesday 13 October" (or a range); within 12 hours of today's cut-off also "Order within 1 h 13 min for delivery tomorrow". No promise: the option's description |
| Checkout, a next-day-only option after the cut-off | Hidden (see below) |
| Product page and basket | For the fastest option with a promise: "Order within 1 h 13 min for delivery tomorrow", or after the cut-off "Order now for delivery on Wednesday 14 October". Nothing when no option makes a promise, or the product can't be bought |
| Order confirmation email, order confirmation page | "Expected Tuesday 13 October" / "Expected between Thu 15 Oct and Mon 19 Oct" |
| Admin order view | A Delivery promise card: option, promised dates, dispatch date and location, and whether it was ordered after the cut-off |
Dates are written in the shop's language (delivery.promise.* messages, including the date
patterns); the words are worked out in Java and templates only print them.
After the cut-off¶
The dates simply move on: "Standard" ordered at 16:30 on Monday is dispatched on Tuesday and says so. An option that only makes sense the same day (Only when it can go today, e.g. "Next-day delivery") is left out once today's cut-off has passed, on a day nothing is sent, or when no dispatch day can be found, because "next day" arriving in two days is a broken promise. The shop can keep it with its later date instead (Still show it after the cut-off). The standard options, with their later dates, are still there.
What is stored¶
When the shopper pays, the chosen option's promise is recorded on the order (Order.deliveryPromise:
option, dispatch date, earliest and latest dates, the cut-off instant, the location and whether it
was after the cut-off), for the emails and pages above and for late-delivery checks once tracking
lands. Orders placed through the older checkout get theirs when the order is placed.
Worked examples¶
A warehouse in London, courier collection Monday to Friday at 17:00, 60 minutes to pick and pack, so a 16:00 cut-off. Two options: Next-day (1 day, only when it can go today) and Standard (2 to 4 days). Bank holidays for England and Wales.
| Ordered | Next-day | Standard |
|---|---|---|
| Monday 12 October, 14:47 | "Arrives Tuesday 13 October", "Order within 1 h 13 min for delivery tomorrow" | "Arrives Wed 14 Oct - Fri 16 Oct" |
| Monday 12 October, 16:30 | Hidden | Dispatched Tuesday: "Arrives Thu 15 Oct - Mon 19 Oct" |
| Friday 16 October, 18:00 | Hidden | Dispatched Monday 19th: "Arrives Wed 21 Oct - Fri 23 Oct" |
| Saturday 17 October | Hidden | Dispatched Monday 19th, as above |
Orders after 3pm on Friday. With "order by 3pm" set as Next-day's own cut-off (15:00), an order at 15:30 on Friday is past it even though the warehouse's own cut-off is 16:00: Next-day is hidden until Monday morning, and Standard is dispatched on Monday. At 14:30 on Friday, Next-day says "Order within 30 min for delivery on Monday 19 October" (no Saturday deliveries), or "for delivery tomorrow" if the option's carrier delivers on Saturdays.
Bank holiday Monday. Monday 31 August 2026 is the summer bank holiday in England and Wales. On Friday 28th at 17:00 the next dispatch day is Tuesday 1 September, so Next-day arrives Wednesday 2nd. At 10:00 on Friday the order goes that day but can't arrive on Saturday, Sunday or the Monday, so it arrives Tuesday 1st. A shop in Scotland (whose summer holiday was 3 August) dispatches on Monday 31st. If the warehouse opens on the bank holiday (special hours 10:00-14:00 that day), it dispatches then, with a 14:00 cut-off (its closing time), but carriers still don't deliver that day.
Christmas. Christmas Day 2026 is a Friday and Boxing Day is moved to Monday 28th. An order at 17:00 on Thursday 24th is dispatched on Tuesday 29th and Next-day arrives Wednesday 30th.
The buffer. With a 17:00 collection and 90 minutes to pick and pack, the cut-off is 15:30. A store that closes at 15:00 on Saturdays, with a 16:00 collection, has a 15:00 cut-off that day. Opening hours that run past midnight don't bring the cut-off forward.
The clocks change. Cut-offs are local times, so 16:00 is 15:00 UTC in summer and 16:00 UTC in winter: an order at 17:00 on Friday 23 October 2026 is promised dispatch on Monday 26th, with a cut-off of 16:00 GMT that day.
Late deliveries¶
An order is late once the end of its last promised day (latest, in the time zone of the
location it was sent from, else the shop's) has passed and it still isn't DELIVERED: submitted,
processing, packing or dispatched. Parcel tracking moves an order to
DELIVERED when the carrier reports every parcel delivered, so a parcel still out there counts as
late. Cancelled and collected orders, and orders that made no promise, are never late.
It is worked out when asked (LateDeliveryService), like the inbox's delivery problems, so nothing
runs on a schedule:
- Ops inbox: MCP
opsInboxcountslateDeliveries(up to 500), with how many hours the longest-late order has been late, next todeliveryProblems. - Admin order page: Late: promised by Tue 13 Oct on the parcel labels card and the delivery promise card.
- POS app: a ship task's
shipping.promisecarries the promised dates, the dispatch date andlate(see Mobile POS).
Example: promised by Tuesday 13 October from a London warehouse, the order is late from midnight British Summer Time (23:00 UTC on the 13th) if it hasn't been delivered.
Capacity is booked on the dispatch date¶
With Store capacity limits on (Enterprise), ship-from-store work counts against the day the order is dispatched, not the day it was placed: an order after the cut-off uses the next dispatch day's capacity. Sourcing ranks stores by the capacity left on that day, and a store full on its next dispatch day isn't promised for it (the promise moves to the following day). See Store operations.
Setting it up¶
| Where | What |
|---|---|
| Admin › Catalogue › Delivery options › option › Delivery promise | Promise a delivery date (on/off), fewest and most days in transit, days the carrier delivers, Only when it can go today, Still show it after the cut-off, Own cut-off |
| Admin › Shop Settings › Locations › location › Courier collection | Last collection for each weekday (blank: nothing collected that day) and the pick and pack time |
| Settings › Shipping and labels › Delivery dates | Bank holiday region and other non-working days |
| MCP | manageDeliveryOptions create/update: transitDays ('1', '2-4', or 'off' to remove the promise), deliveryDays (['MON', ..., 'SAT']), nextDayOnly, showAfterCutoff, cutoffTime ('15:00', '' to clear); deliveryOptions shows each option's promise. manageLocations save: courierCollection (['MON 17:00', ...], replaces) and pickBufferMinutes; locations get shows courierCollection |
| REST | ShopLocation.dispatch (lastCollection per weekday, pickBufferMinutes) on the admin locations API; a save without it keeps the location's |
Promises and courier collection times travel with a site export, as part of the delivery options and locations; the two settings travel with the rest of the site configuration.
Code¶
| Piece | Where |
|---|---|
| Model | DeliveryPromiseRule (DeliveryOptionBean.promise), DeliveryPromise (Order.deliveryPromise), ClosedDates, WorkingDayCalendar in common/.../delivery/; DispatchSettings (Location.dispatch) in common/.../locations/ |
| Calendar | WorkingDayCalendarImpl, delivery/uk-bank-holidays.csv (commerce-core) |
| Engine | DispatchService (next dispatch day and its cut-off; the date capacity is booked on), DeliveryPromiseService (commerce-core/.../delivery/) |
| Checkout | DeliveryService.findDeliveryChoices (options with their promises; next-day-only options hidden after the cut-off), DeliveryChooserExtension, UnifiedCheckoutServiceImpl (DeliveryOptionDTO.estimatedDeliveryLabel, orderWithin; records the promise before taking stock), OrderServiceImpl.submitOrder |
| Words | DeliveryPromiseFormatter; product page and basket line from DeliveryHeadlineService via ProductController and BasketActions (fragment/deliveryPromiseLine) |
| Capacity | StockAllocationServiceImpl (books ship-from-store on the dispatch date), SourcingServiceImpl |
| Admin | DeliveryOptionAdminRenderer, LocationAdminController, templates/admin/orders/view/deliveryPromise.html; OrderAdminController (latePromisedBy) |
| Late | LateDeliveryService (commerce-core/.../delivery/), OrderService.findUndeliveredPromisedBefore; OpsInboxTools (lateDeliveries); PosShipTaskService.promise (PosTaskPromise) |
| MCP | DeliveryTools, LocationTools |
Collection "ready from" another day, collection slots and own-van delivery slots use the same non-working days: see Collection and delivery slots.