Skip to main content

ShipBob Fulfillment KPIs

Status: Draft - Pending Review Data Source: ShipBob BigQuery Integration Implementation: eli-kpi Related: Engagement Metrics 2026


Why These Metrics Exist

The current user journey has a blind spot between purchase and first test. We know 34,579 users purchased, but only 2,238 downloaded the app (6.1%). We don't know whether the drop-off happens because:

  • Kits haven't arrived yet (fulfillment lag)
  • Kits arrived but users never opened them (activation failure)
  • Kits arrived but users couldn't figure out the app (onboarding friction)

ShipBob data fills this gap by providing actual delivery dates, turning the unknown into a measurable funnel stage.


Where ShipBob Fits in the User Journey

The fulfillment stage is the physical bridge between a digital purchase and a physical product in the user's hands. Without measuring it, we're guessing about the entire middle of the funnel.


Data Linking: How ShipBob Connects to Everything

The Complete Chain

Join Keys

FromToJoin FieldTypeMatch Rate
ShipBob reference_idShopify order.idDirect IDINT64100%
Shopify emailFirebase emailCase-insensitiveSTRING~94%
Firebase uidBiometrics accountIdDirect UIDSTRING100%
Biometrics user.idReadings userIdDirect IDSTRING100%

JSON Field Access (ShipBob Orders)

ShipBob data is stored as raw JSON blobs. Key fields extracted via JSON_VALUE():

FieldJSON PathExample
Shopify Order ID$.reference_id6095829762093
Order Status$.statusFulfilled, Delivered
Purchase Date$.purchase_date2026-01-28T19:52:59Z
Recipient Email$.recipient.emailuser@example.com
Delivery Date$.shipments[0].delivery_date2026-02-05T14:30:00Z
Tracking Number$.shipments[0].tracking.tracking_number1Z999AA10...
Carrier$.shipments[0].tracking.carrierUPS
Fulfillment Date$.shipments[0].actual_fulfillment_date2026-01-29T08:00:00Z
Product SKU$.products[0].skuVIP-KIT-CL-2
Quantity$.products[0].quantity1
Lot Number$.shipments[0].products[0].inventory_items[0].lotCB0016
Lot Expiration$.shipments[0].products[0].inventory_items[0].expiration_date2026-11-17T00:00:00
Shipped From$.shipments[0].location.nameElwood (IL)
Shipping Method$.shipping_methodUSA Standard

Proposed KPIs

Overview


KPI 1: Delivery-to-First-Test Time

The Question: After a kit lands on a user's doorstep, how long before they take their first test?

Why It Matters: This is the single most important metric ShipBob unlocks. It separates fulfillment problems (kit hasn't arrived) from activation problems (kit arrived, user hasn't tested). A long delivery-to-test time suggests onboarding friction. A short time suggests product-market fit.

Formula:

Delivery-to-First-Test = First completed reading date - ShipBob delivery date

Output:

MetricDescription
avg_days_to_first_testAverage days from delivery to first test
median_days_to_first_testMedian days (less affected by outliers)
tested_within_7d_pct% of users who tested within 7 days
tested_within_14d_pct% who tested within 14 days
tested_within_30d_pct% who tested within 30 days
never_tested_pct% of delivered kits with no test (30+ days)

Segmentation:

  • By carrier (UPS / FedEx / USPS)
  • By geography (state/province/country)
  • By product SKU
  • By subscription type (monthly / prepaid / one-time)
  • Monthly trend

KPI 2: Post-Delivery Activation Rate

The Question: Of all kits confirmed delivered, what percentage resulted in at least one completed test?

Why It Matters: This is the purest measure of product-market fit. The user has the kit in hand. They either use it or they don't. Unlike the existing engagement metrics that use "purchase date" as the starting point, this uses confirmed delivery.

Formula:

Activation Rate = Users with >= 1 completed test after delivery / Users with confirmed delivery

Time Windows:

WindowDescription
7-dayEarly activation (strong intent)
14-dayStandard activation
30-dayExtended activation

Segmentation: Same as KPI 1.


KPI 3: Fulfillment-Adjusted Test Completion Rate

The Question: What is the real test completion rate when we use actual delivery dates instead of order dates?

Why It Matters: The existing monthly-test-completion-rate-delivered.sql uses order date to attribute "tests delivered" to a month. A January 28 order might not arrive until February 5 — those tests get counted in January's denominator but the user can't possibly take them until February. This inflates January's denominator and deflates its completion rate.

Enhancement:

  • Replace o.created_at (Shopify order date) with sb.shipments[0].delivery_date (ShipBob actual delivery)
  • Falls back to order date when no ShipBob data exists

Formula:

Adjusted Completion Rate = Successful tests this month / Tests actually delivered this month

Where "actually delivered" means ShipBob confirmed the kit was delivered in that month, not just ordered.


KPI 4: Order-to-Delivery Time

The Question: How long does it take from purchase to kit arriving at the customer's door?

Why It Matters: Fulfillment speed directly correlates with activation. Users who wait too long lose motivation. This metric tracks operational efficiency and identifies geographic or carrier issues.

Formula:

Order-to-Delivery = ShipBob delivery_date - Shopify order created_at

Output:

MetricDescription
avg_daysAverage order-to-delivery
median_daysMedian (less skewed by outliers)
p90_days90th percentile (worst-case for most users)
by_carrierBreakdown by shipping carrier
by_regionBreakdown by recipient state/country
monthly_trendTrend over time

KPI 5: Return Rate

The Question: What percentage of fulfilled orders are returned?

Why It Matters: Returns signal product dissatisfaction, shipping damage, or wrong-item issues. Tracking reason codes reveals systemic problems.

Formula:

Return Rate = Returns count / Fulfilled orders count

Output:

MetricDescription
return_rate_pctOverall return rate
by_reasonBreakdown by return reason code
by_productReturn rate per product SKU
monthly_trendTrend over time
avg_return_costAverage return processing cost

KPI 6: Inventory Health (with Lot/Batch Tracking)

The Question: Do we have enough stock to fulfill incoming orders, and where is each batch?

Why It Matters: Stockouts break the funnel entirely — orders can't be fulfilled. Beyond aggregate stock levels, lot-level tracking is critical for regulatory compliance, recall readiness, and expiration management. Eli Health's Cortisol Pack has is_lot: true in ShipBob, meaning every unit is tracked by manufacturing batch.

Output:

MetricDescription
sellable_quantityCurrent sellable inventory per SKU
committed_quantityInventory reserved for pending orders
days_of_stockEstimated days until stockout (sellable / avg daily orders)
backorderedItems on backorder
by_fulfillment_centerStock levels by warehouse (Elwood IL + Brampton ON)
by_lotStock breakdown per lot number
lot_expirationExpiration date per lot
lot_locationWhich fulfillment center holds each lot

Lot-Level Detail:

Each lot record includes:

  • lot_number — Manufacturing batch ID (e.g., CB0014, CB0015, CB0016)
  • expiration_date — Shelf life end date
  • fulfillable_quantity — Available units for that lot
  • onhand_quantity, committed_quantity, awaiting_quantity
  • by_fulfillment_center — Per-lot, per-location stock split

KPI 7: Batch Traceability (Recall Readiness)

The Question: Given a manufacturing lot number, which customers received it? And given a customer, which lot did they receive?

Why It Matters: In a product recall scenario, Eli Health must identify every customer who received a specific manufacturing batch within hours, not days. This is a regulatory requirement for health products. Without this, a recall would require contacting ShipBob manually, cross-referencing spreadsheets, and potentially missing affected customers.

Data Source: ShipBob stores lot information at the shipment level — each fulfilled order records exactly which lot was pulled from the warehouse in shipments[0].products[].inventory_items[].lot.

Capabilities:

Forward Trace (Lot → Customers)

For a recall: given lot number, find all customers who received it.

Input: Lot CB0012
Output: 223 orders, 633 units shipped
→ Customer name, email, address
→ Tracking number
→ Shipped from (Elwood IL or Brampton ON)
→ Order date

Reverse Trace (Customer → Lot)

For a customer complaint: given a customer, find which lot they received.

Input: Customer email
Output: Order #9964, Lot CB0012, 2 units
→ Expiration: 2027-08-12
→ Shipped from: Brampton (Ontario) 2

Batch Funnel (Lot → Usage)

Per-batch metrics: how many units were shipped, how many customers tested, activation rate by batch.

Input: All lots
Output per lot:
→ Total units shipped
→ Total customers
→ Customers who activated (took a test)
→ Activation rate
→ Average days to first test

Output:

MetricDescription
lot_numberManufacturing batch identifier
expiration_dateShelf life for the lot
total_ordersNumber of orders that received this lot
total_units_shippedTotal units shipped from this lot
shipped_fromFulfillment center(s) that shipped this lot
date_rangeEarliest to latest order date for this lot
affected_customersFull customer list (for recall)
activation_rate% of customers from this lot who took a test

Segmentation:

  • By fulfillment center (US vs Canada)
  • By lot number
  • By date range
  • By country/region

Fulfillment Center Reference

Eli Health uses two ShipBob fulfillment centers:

CenterIDCountryRegionActive Lots
Elwood (IL)178USMidwestCB0014, CB0015, CB0016
Brampton (Ontario) 2146CanadaOntarioCB0012

Key fact: Batches can be split across both locations. For example, lot CB0007 was shipped from both Elwood (1,954 units) and Brampton (425 units). A recall for a single lot may require contacting customers in both countries.


Current Lot Inventory (as of Jan 2026)

LotExpiryFulfillableLocationStatus
CB00162026-11-1728Elwood (IL)Low stock, expiring soon
CB00122027-08-12128Brampton (ON)Canada-only stock
CB00142027-10-074,051Elwood (IL)Active
CB00152027-10-314,450Elwood (IL)Active
CB00162027-11-172,296Elwood (IL)Active

Historical lots (fully shipped): CB0007, CB0013, CB0017, CB0018


Lot Data JSON Paths

In Inventory table (eli_health_shipbob.inventory):

FieldJSON PathExample
Lot tracking enabled$.is_lottrue
Lot array$.fulfillable_quantity_by_lotArray of lot objects
Lot number$.fulfillable_quantity_by_lot[N].lot_numberCB0016
Expiration$.fulfillable_quantity_by_lot[N].expiration_date2027-11-17T00:00:00
Fulfillable qty$.fulfillable_quantity_by_lot[N].fulfillable_quantity2296
Per-FC breakdown$.fulfillable_quantity_by_lot[N].fulfillable_quantity_by_fulfillment_centerArray

In Orders table (eli_health_shipbob.orders):

FieldJSON PathExample
Lot (per shipment item)$.shipments[0].products[N].inventory_items[M].lotCB0016
Expiration (per item)$.shipments[0].products[N].inventory_items[M].expiration_date2026-11-17T00:00:00
Quantity from lot$.shipments[0].products[N].inventory_items[M].quantity6
Shipped from$.shipments[0].location.nameElwood (IL)

KPI 8: Lot Expiration Alerts (Slack)

The Question: Are there manufacturing lots sitting in a warehouse that will expire before they can be shipped to customers?

Why It Matters: An expired lot cannot be shipped. If lot CB0016 has 28 units expiring in November 2026 and nobody notices until October, those units are wasted — lost revenue and lost tests that could have helped users. For a health product, this is both a financial and regulatory issue. FIFO (First In, First Out) is the manufacturing standard: ship the oldest lots first.

How It Works:

Alert Rules:

  • Trigger: Any lot with expiration_date within 90 days of today AND onhand_quantity > 0
  • Schedule: Daily at 8:00 AM EST (13:00 UTC)
  • Idempotent: Each lot is only notified once. Uses Slack channel history for deduplication (same pattern as App Store Review Notifier). Each message embeds a lot_alert:{lot_number}:{expiration_date} key in a context block. If a lot has already been posted to Slack, do not post it again.
  • Channel: #alerts-shipbob-inventory (private, finance + operations team)

Slack Message Format:

*Lot Expiration Warning*

Lot *CB0016* (Cortisol Pack) expires on *2026-11-17* (87 days remaining)
Location: Elwood (IL)
Stock remaining: 28 units
Action needed: Ship or transfer before expiration

Lot *CB0012* (Cortisol Pack) expires on *2027-08-12* (if within 90 days)
Location: Brampton (Ontario) 2
Stock remaining: 128 units

Implementation:

  • Run as part of the daily ShipBob sync (after inventory sync completes at 3:00 AM UTC)
  • Or as a separate Cloud Scheduler job at 8:00 AM EST
  • Query eli_health_shipbob.inventory for lots expiring within 90 days
  • Read Slack channel history to check for previously posted lot alerts (deduplication via context blocks)
  • Post via Slack bot (eli_health_alerts bot token)
  • See Lot Expiration Alerts for the full technical spec

Future: Klaviyo Integration for Expiring User Tests

The Idea: If we know which lot a customer received, and that lot is expiring soon, we can proactively email the customer via Klaviyo: "Your cortisol tests expire in 3 months — use them while they're still valid."

Data Chain:

Lot CB0012 (expiring 2027-08-12)
→ Orders table: find all customers who received CB0012
→ Shopify email → Klaviyo profile
→ Trigger Klaviyo flow: "Your tests are expiring"

Prerequisites:

  • Batch traceability queries (already designed above)
  • Klaviyo API integration for triggering flows
  • Marketing team coordination (Morgan) for email copy and timing

Status: Concept — requires further discussion with marketing team.


How These Enhance Existing 2026 KPIs

Existing KPICurrent ApproachShipBob Enhancement
Test Completion Rate (22.2%)Uses order date as "delivery" proxyUse actual ShipBob delivery date for accurate denominator
WAU (16.6%)Eligible = purchased in last 90 daysCould use "kit delivered in last 90 days" for tighter eligibility
MAU Standard (41.1%)Same purchase-based eligibilitySame delivery-based eligibility option
Power Users (13%)No fulfillment contextCorrelate fulfillment speed with power user conversion

Data Sources

Sync Schedule

DatasetSync Time (UTC)MethodFreshness
ShipBob3:00 AMeli-kpi (daily)Orders: 7-day rolling window
ShopifyDailyAirbyteIncremental
LoopWork4:00 AMeli-kpi (daily)Full sync
FirebaseReal-timeNative replicationContinuous
BiometricsReal-timeDatastream CDCContinuous

Priority & Sequencing

PriorityKPIRationale
1Delivery-to-First-Test TimeHighest strategic value; answers the biggest unknown in the funnel
2Post-Delivery Activation RateNew funnel metric for the gap between delivery and engagement
3Batch Traceability (Recall Readiness)Regulatory requirement; recall readiness for health products
4Fulfillment-Adjusted Test Completion RateEnhances existing metric with better data
5Inventory Health (with Lot Tracking)Operational dashboard with batch-level visibility
6Order-to-Delivery TimeOperational health metric
7Return RateQuality signal

Open Questions

  1. Delivery date availability: What percentage of ShipBob orders have a populated delivery_date? Some carriers may not report delivery confirmation. Need to validate data completeness after first sync.

  2. Multiple shipments per order: Some orders may have multiple shipments (partial fulfillment). Should we use the first shipment's delivery date, the last, or track per-shipment?

  3. Eligibility window adjustment: Should the 90-day eligibility window for WAU/MAU be measured from delivery date instead of purchase date? This would be a more accurate "time since kit in hand" window.

  4. Return handling in activation: If a kit was delivered, returned, and reshipped — does the activation clock restart? How do we handle this edge case?

  5. International vs domestic: ShipBob shows shipping_method (e.g., "USA Standard"). Should we segment KPIs by domestic vs international shipping?

  6. Batch/lot availability: Does ShipBob track lot numbers? RESOLVED: Yes. The Cortisol Pack has is_lot: true. Lot number, expiration date, and per-fulfillment-center breakdown are all available in the fulfillable_quantity_by_lot array (inventory) and shipments[].products[].inventory_items[].lot (orders). Every fulfilled order records which lot was used.

  7. Multiple fulfillment centers: Do batches get split across locations? RESOLVED: Yes. Eli Health uses Elwood (IL) for US and Brampton (Ontario) for Canada. Batches can be split — e.g., CB0007 shipped from both locations. A recall query must cover both centers.


Revision History

DateAuthorChange
2026-01-29ChipInitial specification
2026-01-30ChipAdded KPI 7 (Batch Traceability), KPI 8 (Lot Expiration Alerts), expanded KPI 6 with lot tracking, documented lot data JSON paths, fulfillment center reference, current lot inventory, and Klaviyo integration concept