Metric

What is on-time delivery rate?

Definition

On-time delivery rate is the share of shipments delivered on or before the promised date, measured over shipments with a known outcome. The denominator is where honesty lives: shipments with tracking coverage and an actual delivery scan. Anything without a delivery scan is unknown, not on time, and the coverage percentage belongs on the same card as the rate.

Formula: On-time delivery rate = shipments delivered on or before the promised date / shipments with a delivery scan × 100

What data do you need?

  • Labels with ship date and the delivery promise captured at ship time
  • Tracking events with timestamps, including a delivered scan and the first carrier acceptance scan
  • Carrier, service level, and destination zone
  • Exception events (delivery attempts, weather holds, address issues)
  • Order timestamps so handoff time can be separated from transit time

SQL pattern

On-time rate with tracking coverage and handoff timePostgreSQL
WITH shipped AS (
  -- Snapshot the promise at ship time. Carrier estimated delivery
  -- dates move after purchase; reading today's EDD makes every late
  -- shipment look on time retroactively.
  SELECT
    label_id,
    order_id,
    carrier,
    service_level,
    shipped_at,
    promised_delivery_date
  FROM shipping_labels
  WHERE status NOT IN ('voided', 'refunded')
    AND shipped_at >= CURRENT_DATE - INTERVAL '90 days'
    AND promised_delivery_date IS NOT NULL
), outcome AS (
  SELECT
    label_id,
    MIN(scanned_at) FILTER (WHERE event_type = 'accepted')
      AS first_carrier_scan_at,
    MAX(scanned_at) FILTER (WHERE event_type = 'delivered')
      AS delivered_at,
    COUNT(*) FILTER (WHERE event_type = 'exception') AS exceptions
  FROM shipment_tracking
  GROUP BY 1
)
SELECT
  s.carrier,
  s.service_level,
  COUNT(*) AS shipments,
  -- Coverage is part of the metric, not a footnote: a 98% on-time
  -- rate over 60% coverage is not a 98% on-time rate.
  ROUND(
    100.0 * COUNT(*) FILTER (WHERE o.delivered_at IS NOT NULL)
    / NULLIF(COUNT(*), 0), 1
  ) AS delivery_scan_coverage_pct,
  ROUND(
    100.0 * COUNT(*) FILTER (
      WHERE o.delivered_at::date <= s.promised_delivery_date
    ) / NULLIF(COUNT(*) FILTER (WHERE o.delivered_at IS NOT NULL), 0), 1
  ) AS on_time_pct,
  -- Late handoff is your problem, not the carrier's. Long hours from
  -- ship record to first carrier scan means the warehouse is behind.
  ROUND(
    AVG(EXTRACT(epoch FROM o.first_carrier_scan_at - s.shipped_at) / 3600)
      FILTER (WHERE o.first_carrier_scan_at IS NOT NULL), 1
  ) AS avg_handoff_hours,
  ROUND(
    AVG(o.delivered_at::date - s.promised_delivery_date)
      FILTER (WHERE o.delivered_at::date > s.promised_delivery_date), 1
  ) AS avg_days_late_when_late,
  ROUND(AVG(COALESCE(o.exceptions, 0)), 2) AS avg_exceptions
FROM shipped s
LEFT JOIN outcome o USING (label_id)
GROUP BY 1, 2
ORDER BY shipments DESC;

Common pitfalls

Treating shipments with no delivery scan as on time.→ They have no known outcome. Exclude them from the numerator and denominator, and publish the coverage rate next to the result so nobody reads a thin sample as a strong one.
Comparing delivery against today's estimated delivery date.→ Carriers revise the EDD mid-transit, often to match reality. Store the promise made at ship time and compare against that frozen value.
Blaming the carrier for late orders you shipped late.→ Measure time from order to first carrier scan separately. If handoff takes 40 hours, the carrier hit its window and the customer still got the package late.
Reporting one blended rate across services and zones.→ Ground to a far zone and overnight air have completely different failure profiles. Break the rate down by carrier, service level, and zone before drawing conclusions.

Where does this metric apply?

This metric commonly uses data from Shippo, ShipStation, EasyPost, AfterShip, plus any warehouse models built at the same grain.

Dashboards

Integrations

Metrics

Analytics

FAQ

Should the promise be the carrier's estimate or ours?
Track both. The carrier estimate is how you hold carriers accountable; the date shown to the customer at checkout is how customers judge you. When your checkout promise is more aggressive than the carrier estimate, the gap is a support-ticket generator and worth seeing on the dashboard.
How does on-time delivery affect the rest of the business?
Late deliveries show up downstream in support volume, refunds, and repeat purchase rate. Trending on-time rate against repeat purchase by cohort is the cleanest way to price how much fulfillment reliability is actually worth. See the shipping cost dashboard for the cost side of the same trade-off.