ShippyPro Blog - Shipping Solutions Library

Predicting Delivery Times at Scale: From Order Creation to Last Mile

Written by Amir Alamo | Nov 3, 2026, 11:44:56 AM

Introduction

Millions of parcels are shipped across the world every day, each following a different path through a network of carriers, sorting facilities, and last-mile couriers. Predicting when any single parcel will arrive means accounting for carrier behavior, geographic distance, border crossings, local holidays, and real-time transit conditions — all while the shipment is still in motion.

The Problem

The prediction problem has two faces — but as we'll show, they're really the same problem at different points in time.

At checkout, a customer selects a shipping method and expects to see a delivery date. At this point, we know the origin, destination, carrier, and service type — and nothing else. No tracking events, no transit history, no signal about how this specific shipment will behave. We need to predict delivery time from static attributes alone, in real time, while the customer is waiting.

Once the parcel ships, the problem seems to change entirely. Tracking events start streaming in — pickup scans, hub arrivals, customs holds, out-for-delivery updates. Each event narrows the uncertainty. A parcel stuck at customs for 12 hours should get a later estimate; one that cleared in 2 hours should get an earlier one. Now the model has a growing sequence of observations, and the prediction should sharpen with each one.

The obvious approach would be to build two separate models: a static regression for checkout, and a sequence model for transit. But we realized something: the moment an order is created is itself the first event in the sequence. A checkout prediction is just a sequence prediction where the sequence has length one — a single timestep representing "order just created, zero hours elapsed, status: created." As real tracking events arrive, the sequence simply grows.

This insight shaped every design decision that followed. Instead of two models with two pipelines, we built a single LSTM that produces a prediction at every timestep. At checkout, it processes one event. After pickup, two. After customs clearance, five. The same model, the same features, the same endpoint — the only thing that changes is the length of the input.

Figure 1: Pre-Order vs Post-Order

The remaining challenges are about making this work at scale: generalizing across dozens of carriers with different operational patterns, handling cross-border shipments with customs delays and different holiday calendars, and serving predictions at two very different latency profiles — sub-second for the checkout API, and batch throughput for a continuous stream of tracking events.

The most common approach to delivery time prediction in the logistics industry is carrier-provided ETAs. Carriers publish estimated delivery windows based on service level agreements — "Express: 1-2 business days." The problem is that these are contractual targets, not predictions. They don't account for actual operational conditions: a sorting facility running behind schedule, a customs backlog at a border crossing, or a holiday weekend that effectively removes a business day.

The next step up is a static regression model — take historical delivery data, extract features like origin, destination, carrier, service type, and day of week, and train a gradient-boosted model (XGBoost, LightGBM) to predict delivery time. We experimented with this approach early on, and it works reasonably well for the pre-order case. But it has a fundamental limitation: it can't incorporate tracking events. Once the parcel is in transit and you're watching it move through the network, a static model has no way to use that information. The prediction at hour 0 is the same as the prediction at hour 48, regardless of what happened in between.

What we needed was a model that starts with a static prediction and gets better over time as observations accumulate. That naturally points to a sequence model — and specifically, to an architecture that produces an updated prediction at every timestep, not just at the end of the sequence.

Architecture

The core of the system is a dual-branch LSTM that takes two inputs at every prediction: a sequence of tracking events and a static context vector describing the order itself.

The static branch encodes four categorical features — carrier, service type, origin country, destination country — through learned embeddings, and concatenates them with 14 numeric features like distance, shipment volume, and cyclic time encodings. This produces a fixed-size context vector that captures what kind of shipment this is.

Figure 2: LSTM architecture.

The sequence branch processes the tracking event history through a 2-layer LSTM. Each timestep carries 15 features: time since order creation, time since the previous event, carrier performance at the origin and destination, holiday flags, and a learned embedding for the status code. The LSTM handles variable-length sequences natively using packed sequences — no wasted computation on padding. At every timestep, the LSTM output is concatenated with the static context and passed through a fully-connected head that outputs two values: μ (predicted delivery time) and log σ² (prediction uncertainty). This is a probabilistic prediction — the model doesn't just say "3.2 days," it says "3.2 days ± 0.8 days," giving downstream systems a confidence signal.

The model is trained with Gaussian negative log-likelihood loss, which jointly optimizes both the mean prediction and the uncertainty estimate. We clamp the log-variance to prevent the model from collapsing to zero uncertainty or exploding — a practical necessity when training on delivery times that range from 12 hours to 3 weeks. The target itself is transformed with log(1 + hours) before scaling, which stabilizes training on a heavily right-skewed distribution.

Figure 3: End-to-End process.

The system has three stages. Preprocessing runs daily: Glue ETL jobs clean and enrich raw shipment data from the data lake, then a SageMaker Processing job converts flat records into variable-length sequences for training. The trained model is deployed to a SageMaker real-time endpoint, which serves both prediction paths. Pre-order predictions arrive synchronously via API Gateway and return in under a second. Post-order predictions flow through SQS, where tracking events are batched, deduplicated, and enriched with order metadata from DynamoDB before invoking the same endpoint.

Features

Each prediction combines two types of input. The static branch captures what we know about the order at creation: carrier, service type, origin and destination countries, distance, and time-of-day encodings. The sequence branch captures what happens during transit: at each tracking event, we encode the time elapsed since order creation, the gap since the previous event, the status code, holiday flags, and — the most predictive signal we found — carrier performance statistics at the geographic level. We tile the map into H3 hexagonal cells and compute rolling average delivery times per carrier, per region. This gives the model a localized prior: a carrier that's been slow in northern Germany this week will get a longer estimate there, even if their national average looks fine.

What the Model Learned

To validate that the LSTM learned meaningful patterns — rather than memorizing noise — we ran three interpretability analyses on 1.38 million test orders. Permutation feature importance revealed that sequential features dominate: the top five are all sequence-level, led by destination carrier performance (+24.9% MAE degradation when shuffled) and status group (+24.5%). Static features like distance and country contribute surprisingly little on their own — the model relies on what's happening during transit, not just where the parcel is going. Embedding visualization confirmed that the learned representations capture real-world structure: carrier embeddings cluster by operational similarity (UPS, UPS Turkey, and DPD Belgium land close together; Polish carriers InPost and Orlen Paczka are nearly identical), and country embeddings group geographically (New Zealand and Australia have 0.99 cosine similarity). Most telling was the sequence length analysis: prediction accuracy improves sharply from 1 event (15.2h MAE) to 2 events (7.0h) to a sweet spot at 3–5 events (3.0h), confirming that each tracking scan genuinely narrows the model's uncertainty. Interestingly, orders with 30+ events show worse accuracy — not because the LSTM struggles with long sequences, but because those orders are the stuck, delayed, and returned shipments that are inherently harder to predict.

Figure 4: Country Embeddings: Geography Emerges from delivery pattern.

Lessons Learned

Keeping static features out of the LSTM was the key architectural decision. Our first instinct was to feed everything — carrier, service type, country, distance, along with tracking events — into the LSTM. The idea was that the LSTM would learn carrier-specific behaviors: "when DHL shows status X, it means something different than when GLS shows status X." But the results were worse. With 78 static dimensions added to every timestep, the LSTM was overwhelmed — it couldn't separate the tracking signal from the static noise. The breakthrough came when we pulled static features out of the LSTM entirely and gave them their own branch. The LSTM now only sees the sequence: timestamps, status codes, and carrier performance at each event. It learns the universal pattern of how deliveries progress — the rhythm of scans, the meaning of delays between events — without being distracted by which carrier or which route. The static context (carrier, service, countries, distance) is concatenated at the prediction head, where a simple FC layer adjusts the LSTM's sequence-based estimate for the specific shipment. Separation of concerns, applied to neural network design.

What's Next

The current system handles the core prediction well, but there are several directions we're exploring:

  1. Real-world event features. The model currently sees only tracking events and carrier history. But delivery times are also affected by weather, carrier strikes, port congestion, and peak shopping seasons like Black Friday. Integrating external signals — weather forecasts at the destination, known strike calendars, historical peak-season delay patterns — could help the model anticipate delays before they show up in tracking data.
  2. Attention over timesteps. Replacing or augmenting the LSTM with a temporal attention mechanism would let us see which events the model weighs most heavily — not just that more events help, but which specific scans matter. This also opens the door to better handling of long, noisy sequences.
  3. Long-tail deliveries. The model under-predicts extremely slow shipments (customs holds, returns, lost parcels). These are rare in the training data but disproportionately important for customer experience. We're evaluating quantile regression and weighted loss functions to improve coverage on the tails.

Conclusion

Predicting delivery times is fundamentally a sequence problem — and the key insight that unlocked our design was recognizing that the moment of order creation is simply the first event in that sequence. This let us serve both checkout predictions and in-transit updates with a single LSTM, avoiding the operational cost of maintaining two separate models.

The system has been running in production across dozens of carriers and multiple European markets. The interpretability work confirmed that the model learned meaningful structure: carrier performance and tracking events drive predictions far more than static route features, and the learned embeddings capture real-world relationships between carriers, countries, and shipment statuses that we never explicitly encoded.