hose Demo data with a pulse

A claim about Quenchly

Annual cohorts retain more than 100% of their revenue while losing accounts.

Pooled across annual cohorts with a full year of history, net retention lands between 100% and 115% and gross retention between 88% and 94%; cohorts whose twelfth month falls after the year-three price increase sit at the top of that range. Monthly cohorts sit lower on both.

holds on the current window

as of moments ago

Net and gross retention by billing

WITH price AS (
  SELECT id,
         seat_price_usd_month        AS monthly,
         seat_price_usd_year / 12.0  AS annual_per_month
  FROM plans
),
activation AS (
  SELECT e.subscription_id, s.billing, e.at AS t0,
         strftime('%Y-%m', e.at) AS cohort,
         e.seats * CASE s.billing WHEN 'annual' THEN p.annual_per_month ELSE p.monthly END AS mrr0
  FROM subscription_events e
  JOIN subscriptions s ON s.id = e.subscription_id
  JOIN price p         ON p.id = e.plan_id
  WHERE e.kind = 'activate'
    AND datetime(e.at, '+12 months') <= datetime((SELECT MAX(at) FROM subscription_events))
),
state12 AS (
  SELECT a.*,
    (SELECT e.plan_id FROM subscription_events e
      WHERE e.subscription_id = a.subscription_id
        AND e.kind IN ('activate', 'plan_change', 'seats_change')
        AND datetime(e.at) <= datetime(a.t0, '+12 months')
      ORDER BY e.at DESC LIMIT 1) AS plan12,
    (SELECT e.seats FROM subscription_events e
      WHERE e.subscription_id = a.subscription_id
        AND e.kind IN ('activate', 'plan_change', 'seats_change')
        AND datetime(e.at) <= datetime(a.t0, '+12 months')
      ORDER BY e.at DESC LIMIT 1) AS seats12,
    EXISTS (SELECT 1 FROM subscription_events e
      WHERE e.subscription_id = a.subscription_id
        AND e.kind = 'cancel'
        AND datetime(e.at) <= datetime(a.t0, '+12 months')) AS gone
  FROM activation a
),
priced AS (
  SELECT s.billing, s.cohort, s.mrr0,
         CASE WHEN s.gone THEN 0
              ELSE s.seats12 * CASE s.billing WHEN 'annual' THEN p.annual_per_month ELSE p.monthly END
         END AS mrr12
  FROM state12 s JOIN price p ON p.id = s.plan12
)
SELECT billing,
       COUNT(*)                                        AS accounts,
       ROUND(100.0 * SUM(mrr12) / SUM(mrr0), 1)        AS net_retention_pct,
       ROUND(100.0 * SUM(MIN(mrr0, mrr12)) / SUM(mrr0), 1) AS gross_retention_pct
FROM priced
GROUP BY billing;
billingaccountsnet_retention_pctgross_retention_pct
annual650107.489.5
monthly77972.361.3

holds on the current window

Net revenue retention above 100% is the SaaS number everyone quotes and few datasets can produce, because it needs three things at once: accounts that leave, accounts that add seats, and the two happening to the same cohort over the same year. Quenchly has all three. Seat expansion is driven by a per-account trait, bigger teams churn less, and annual billing concentrates exits at renewal. Take the accounts that activated in one month, price them at activation and again twelve months later, and the survivors’ growth outweighs the leavers.

What you should see

Two rows. The annual row has net retention above 100 and gross retention in the low 90s. The monthly row is lower on both, because monthly subscribers can leave any month and most exits happen early. Add cohort to the GROUP BY to see the spread; individual months swing by several points, which is what real cohort tables look like.

Run it yourself

More on Quenchly