River
Y CombinatorBacked by Y Combinator
FREE TEMPLATE

Product Version Migration Plan

A migration announcement sent to everyone reaches 1,238 accounts with nothing to migrate and manufactures the tickets it was meant to prevent.

Free download  ·  No account needed

This pack derives the cohorts from event data on the exact capability being retired, then joins them against the capability gap between the old version and the new one. Usage alone gives you heavy users. The gap list alone gives you missing features. The join gives you the accounts that cannot move today, who they are, and which gap is holding each one. In the worked example that is 51 accounts out of 1,842.

Deprecation policy is a solved problem in infrastructure and an unsolved one in product. Kubernetes fixes the window in its own policy text: a beta API is no longer served nine months or three minor releases after deprecation, whichever is longer. Microsoft's Modern Lifecycle Policy commits to a minimum of twelve months of notice before support ends where no successor is offered. Both set the date. Neither tells you who to tell first, which is the part that decides the support load.

In the worked example, 61.7 percent of the heaviest cohort is blocked against 1.3 percent of the lightest, so a single announcement reaches both with the same message. Staging it costs 189 tickets against 268, and the peak week needs one support person rather than five. Built to sit after the spec for the replacement and beside the stories engineering is building from, for the fortnight before a retirement date goes public. It is not a sunset with no replacement, which is a different job.

Fourteen capabilities, 604 users, 51 accounts that cannot move

The gap map, the usage-derived cohorts it joins to, and the support load priced both ways before a single message is written.

Capability Gap Map

Illustrative, for a fictional field-services product called Torbeck retiring its v1 report builder. Usage counted over 90 days across the 1,842 accounts entitled to the feature.

v1 capabilityVerdictAccounts using itBlocked by this aloneResolution
C-01 to C-05, C-07 to C-09, eight capabilitiesCovered, same shape96 to 6040Nothing to do
C-06 PDF export with the company logoCovered, changed shape2410Branding moves to account settings
C-10 Restrict a report to named peopleCovered, changed shape2050Per role rather than per report
C-11 Calculated column with a formulaCovered, changed shape1320New syntax, old formulas show an error
C-12 Scheduled delivery to people without a loginNot covered3830Ships week 6
C-13 Custom SQL block inside a reportNot covered127Not being rebuilt
C-14 Legacy fixed-width payroll exportNot covered96Not being rebuilt
14 capabilities11 covered, 3 not59 on the three gap rows43 single-gap51 distinct accounts

Two things only this table shows. The three uncovered rows carry 59 memberships and 51 distinct accounts, because eight accounts use two of them, so a naive join overstates the affected population by 16 percent. And the three middle rows, covered but changed, block nobody at all: they appear on no list of what is missing, they are the reason a support brief exists, and together they account for most of the ticket volume in the whole migration.

Affected Cohort Analysis

Cohorts derived from report_run events over the 90 days to 12 March, then joined against the three uncovered capabilities. Plan tier and seat count are not used anywhere in this sheet.

CohortDerived fromAccountsBlockedBlocked rateWhat they are told
Deep20+ runs472961.7%Named owner, individually, week 0
Regular5 to 19 runs1631710.4%Blocked ones week 0, rest week 2
Occasional1 to 4 runs39451.3%Blocked ones week 0, rest weeks 4 and 5
Never opened it0 runs1,23800.0%Nothing. Release notes only
Entitled to the featureplan data, not usage1,842512.8%30 unblock on a date, 21 need a person

The slope is the finding, and it is the opposite of what a single announcement assumes. The heaviest cohort is 49 times more likely to be stuck than the lightest, because the capabilities v2 has not reached yet are the ones only sustained use ever finds. Note the two ways to say the same fact: 51 of 1,842 is 2.8 percent of the base and reads like a mailing exercise, while 29 of 47 is most of the heaviest cohort and reads like a project. Which sentence leads decides how this gets resourced.

Support load, priced both ways

Ticket rates carried from Torbeck’s previous retirement: 1.40 per blocked account contacted cold, 0.35 per blocked account reached by a named owner first, 0.31 per unblocked user, 0.02 per account that never used the feature.

ApproachBlockedUnblocked usersNever opened itTicketsConcentration
One announcement to everybody entitled51 x 1.40553 x 0.311,238 x 0.02267.6187 inside 72 hours, 62 a day, 5.2 people
Usage-derived and staged over six weeks51 x 0.35553 x 0.31no send189.360.5 in the peak week, 12 a day, 1.0 person

The 78.3-ticket difference is two separate arguments and they should be made separately. 24.8 tickets are manufactured by emailing 1,238 accounts with nothing to migrate. 53.5 are saved by reaching the 51 blocked accounts through a person before they read about it in a mass send. The totals matter less than the shape: a team closing about 12 migration tickets each per day needs five people for three days under one approach and one person for six weeks under the other.

WaveWeekWhoAccountsChannelExpected tickets
W-00Blocked, every cohort51Named owner, individual, call for 21 of them17.9
W-11Deep, unblocked18Email and in-product, person offered5.6
W-22Regular, unblocked146Email and in-product45.3
W-34Occasional, first half195Email60.5
W-45Occasional, second half194Email60.1
nonen/aNever opened it1,238No send, in-product notice only0
Total0 to 5Accounts contacted6045 waves189.3

The 389 occasional accounts are split across two weeks for one reason: 389 in a single week exceeds what four people can close, and two halves of roughly 195 do not. The split earns something else as well. Week 4’s tickets are read before week 5 goes out, so a sentence that turns out to be missing gets fixed once rather than answered 194 more times.

What is in the pack

01

Capability Gap Map with usage on every row

Every capability of the old version, whether the new one covers it, how many accounts used it in the window, and how many of them it is the only thing blocking.

02

The middle category nobody names

Capabilities that survive and change shape. They block nobody, they appear on no list of what is missing, and they are the leading source of tickets in a typical migration.

03

Cohorts derived from events, not from plan tier

Deep, regular, occasional and never opened it, with the event count and the window written into the sheet so nobody has to remember what a cohort meant six weeks later.

04

The join, reported as a rate per cohort

Blocked accounts inside each usage band, plus the split between the ones a ship date fixes and the ones that need a named person and a call.

05

Support load priced before the comms are drafted

One announcement against staged waves, using rates from whatever you retired last, converted into people per day rather than a total nobody can act on.

06

Objection Register with a count and an answer

Every objection with who raised it, how many accounts have raised it, the answer given, and whether the plan changed. Two normally should.

How it works

  1. 1

    Send the change

    Whatever engineering wrote, especially anything describing what the new version cannot do yet.

  2. 2

    Add the usage export

    Account id and an event count over ninety days is enough. An entitlement list is not a substitute.

  3. 3

    Read the join

    Blocked accounts by usage cohort, deduplicated across gaps, split into the ones a date fixes and the ones that need a person.

  4. 4

    Price it, then write

    One announcement against staged waves, in people per day, and the wave plan falls out of the number.

Frequently asked questions

What do I need before this is useful?

Whatever engineering wrote about what the new version does not do yet, plus a usage export for the feature being retired. Account id and an event count over ninety days is enough. An entitlement list is not a substitute: those two populations differed by 1,238 accounts here. An adoption review is one place to get the export.

Why not just announce it to everybody?

Because two thirds of the entitled base has nothing to migrate, and contacting them manufactures tickets. At the previous retirement's rate that is about 25 tickets from 1,238 accounts, none matching any migration work. The bigger cost is concentration: one send lands 187 tickets in 72 hours, which is five people on a team of four.

Is it safe to send nothing to the accounts that never opened it?

They have zero events in the window, so there is no saved work to move and no schedule to rebuild. The change still goes in the release notes and the changelog, and anybody who opens the old surface sees an in-product notice from week zero. That is the safety net.

Our heaviest users are the ones who get hurt. Is that normal?

It is structural rather than bad luck. The capabilities a new version has not reached yet are the ones only sustained use ever finds, so exposure rises with commitment. In the worked example 29 of 47 deep users were blocked against 5 of 394 occasional ones, a 49-fold difference a single announcement cannot address.

What actually generates the most tickets?

Not the missing capabilities, because those accounts already have a named owner. It is the capabilities that survived and changed shape: branding moved to account settings, permissions moved to roles, formula syntax changed. All three are covered, none appears on a gap list, and together they explain most of the volume.

Does this set the retirement date?

The other way round. The date is a decision the gap map should inform, so nothing goes in the sheets until the join exists. Committing first is how a plan promises a date while thirty accounts wait on something unbuilt. Engineering's own estimates belong in a feasibility brief before the date is fixed.

Find out how many accounts actually cannot move

Send the change and a usage export for the thing being retired. What comes back first is the gap map joined to the cohorts, and the blocked rate inside each one.

Build my cohorts