I sent you this chapter. Reply on LinkedIn or by email.
The formulas behind the tools, and four queries for the scorecard.
| For | Formula | Notes |
|---|---|---|
| Blended repeat rate | R = u × ru + (1 − u) × rn | u: share used early. ru, rn: repeat rates of users and non-users. |
| Lift from moving m to used | ΔR = m × (ru − rn) × c | m: share of all buyers moved. c: share of the gap caused by use; estimate it with a holdout. |
| Break-even first-use spend | ΔR × V per first-time buyer | V: contribution per repeat customer over 12 months. Yearly value is N × 12 × ΔR × V. |
| Corrected share used by day t | pt × (q + (1 − q)(1 − k)) | q: share of the cohort you heard from. k: how much less likely the rest are to have used it. |
| Median time to first use | Linear interpolation between the two days where the corrected share crosses 50% | From (0, 0) through days 1, 3, 7, 14 and 30. |
| Fair captivity | (H + S + W) / 18 × (1 − R / 12) | Each source 0 to 6 from three statements; R is resentment, 0 to 6. A judgment weighting, not a published scale. |
-- first-time buyers, whether they used it within 14 days of delivery,
-- and whether they ordered again within 180 days of the first order.
-- usage_events: one row per signal of use (survey answer, activation,
-- QR scan), with customer_id, used_at, source
WITH first_orders AS (
SELECT o.customer_id, o.id AS order_id, o.created_at,
MIN(f.delivered_at) AS delivered_at
FROM orders o
JOIN fulfillments f ON f.order_id = o.id
WHERE o.customer_order_index = 1 -- or ROW_NUMBER() over created_at
AND o.created_at < CURRENT_DATE - INTERVAL '180 days'
GROUP BY 1, 2, 3
),
use AS (
SELECT fo.customer_id,
MIN(u.used_at) AS first_used_at
FROM first_orders fo
JOIN usage_events u ON u.customer_id = fo.customer_id
AND u.used_at >= fo.delivered_at
GROUP BY 1
),
repeaters AS (
SELECT DISTINCT fo.customer_id
FROM first_orders fo
JOIN orders o2 ON o2.customer_id = fo.customer_id
AND o2.created_at > fo.created_at
AND o2.created_at <= fo.created_at + INTERVAL '180 days'
)
SELECT CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '14 days'
THEN 'used by day 14' ELSE 'not used / unknown' END AS grp,
COUNT(*) AS buyers,
AVG(CASE WHEN r.customer_id IS NOT NULL THEN 1.0 ELSE 0 END) AS repeat_rate_180d
FROM first_orders fo
LEFT JOIN use u ON u.customer_id = fo.customer_id
LEFT JOIN repeaters r ON r.customer_id = fo.customer_id
GROUP BY 1;
Buyers with no signal fall into the second group, which mixes non-users with users you didn’t hear from. Run it again restricted to buyers who answered the survey, and use that version in the tool; the unrestricted one understates the gap.
-- cumulative share used by day 1, 3, 7, 14, 30 after delivery,
-- among first-time buyers who answered the use question
-- (reuses the first_orders and use CTEs from the query above)
SELECT DATE_TRUNC('month', fo.delivered_at) AS cohort,
COUNT(*) AS respondents,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '1 day' THEN 1.0 ELSE 0 END) AS by_d1,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '3 days' THEN 1.0 ELSE 0 END) AS by_d3,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '7 days' THEN 1.0 ELSE 0 END) AS by_d7,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '14 days' THEN 1.0 ELSE 0 END) AS by_d14,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '30 days' THEN 1.0 ELSE 0 END) AS by_d30
FROM first_orders fo
JOIN survey_responses s ON s.customer_id = fo.customer_id
AND s.question = 'first_use'
LEFT JOIN use u ON u.customer_id = fo.customer_id
GROUP BY 1
ORDER BY 1;
Map survey answers to a date before this runs: “today” is the response date, “1 to 3 days after it came” is delivery plus 2 days, and so on; “not yet” leaves first_used_at empty. Divide respondents by the cohort’s delivered first orders to get coverage for the tool in chapter 3.
-- share of repeat buyers whose latest gap is within 25% of their median gap
WITH gaps AS (
SELECT customer_id, created_at,
created_at - LAG(created_at) OVER (PARTITION BY customer_id ORDER BY created_at) AS gap
FROM orders
),
per_customer AS (
SELECT customer_id,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM gap)) AS median_gap,
(ARRAY_AGG(EXTRACT(EPOCH FROM gap) ORDER BY created_at DESC))[1] AS last_gap,
COUNT(gap) AS n_gaps
FROM gaps WHERE gap IS NOT NULL
GROUP BY 1
)
SELECT AVG(CASE WHEN ABS(last_gap - median_gap) <= 0.25 * median_gap THEN 1.0 ELSE 0 END) AS regular_share
FROM per_customer
WHERE n_gaps >= 3;
-- reorders from a saved cart, reorder link or subscription
SELECT DATE_TRUNC('week', created_at) AS week,
AVG(CASE WHEN landing_site LIKE '%utm_content=reorder%'
OR source_name = 'subscription'
THEN 1.0 ELSE 0 END) AS one_tap_share
FROM orders WHERE customer_order_index > 1
GROUP BY 1 ORDER BY 1;
Want to answer the note that sent you here? Reply on LinkedIn or by email.
If this is your store
The free Klaviyo audit reads your own account and scores it. Start with your store’s address. A read-only key gets the full report.
Written by Andrew Lauchner, a growth and retention operator for consumer brands. The paid work is one ninety-day Sprint.
This is one chapter of Used, Not Bought, which is free and readable in full on a single page with no form in front of it.