---
id: saas/expansao-upsell
industry: SaaS and technology
usage: specific
effort: medium
grain: Customer x product x month; company at repurchase timing
---

# Expansion, upsell and repurchase timing

How much new revenue came from the base, which customers still don't have the module that makes sense and who is at the moment to buy again.

## Executive summary

**For:** SaaS companies with more than one module or plan, or with recurring sales to the same base, that want to grow revenue from current customers.

**What it solves**

- Expansion separated from plan migration and billing-cycle changes.
- For each module, who has it, who doesn't and who should.
- A repurchase queue at the right time, based on each customer's cycle.

**Why it matters:** Expansion is the cheapest NRR lever, but it disappears in the total: upsell becomes a new sale, a monthly-to-annual switch becomes expansion and cross-sell doesn't show by product. Without knowing who has what, CS offers the module to whoever already bought it and forgets whoever is ready to buy.

**Business questions it answers**

- How much expansion MRR did we have this quarter, and in which products?
- Which customers of the core product still don't use module X?
- Which companies are at repurchase timing this month?
- How much of this month's new revenue came from the base?
- What is each module's attach rate by plan?
- Which accounts have the most upsell potential for CS to work?

**What you need**

- Billing with the items and products of each subscription or charge
- Subscription history (daily snapshot or change events)
- The CRM's expansion and CS pipelines and the list of segments that go into re-engagement

**What you get**

- An MRR waterfall by customer and product, with upsell and cross-sell flagged
- An attach matrix by module and plan
- Upsell and repurchase queues by account owner
- An AI agent that answers about expansion and who is ready to buy

## Detailed

### The problem

Billing keeps the current state: when the customer increases quantity or adds a module on the same subscription, yesterday's snapshot is overwritten and the expansion disappears. When they cancel and re-sign to change plans, it shows up as churn and a new customer. And the upsell closed in the CRM goes into the new-business target. Nobody knows how much the base grew, or whom to offer the next module.

### Business rules

1. **Expansion is a difference by customer and product.** Compute each customer's MRR by product at month end and compare it with the previous month end. An increase in a product the customer already had is upsell. MRR in a product the customer didn't have, while already paying, is cross-sell: expansion flagged as a new product. The product lens adds up to the customer lens, and both use the same month boundary. (Avoid: Summing the MRR of new items in the month, without comparing with last month.)
2. **Capture changes inside the subscription.** Quantity changes, price changes, items added or removed and discounts applied on the same subscription don't show in a table that keeps only current state. Use subscription change events or a daily snapshot of items. Events complement the snapshot, they don't replace it: new and canceled subscriptions still come from the snapshot, so nothing is counted twice. (Avoid: Measuring expansion only from new subscriptions.)
3. **Migration is not expansion.** Switching from monthly to annual, a cycle renewal and canceling and re-signing to change plans are migrations of the same customer. Separate natural expansion from migration in a column: migration moves MRR but isn't a sale to the base. A renewal with the same quantity and price is not a movement. (Avoid: Counting a cycle switch or a re-sign as upsell.)
4. **Expansion without a charge is suspicious.** On a monthly plan, expansion with no paid charge in the month points to an early setup or a wrong item: flag it for review. On an annual plan, expansion in a month without a charge is normal. Customers from channels that don't create invoices in billing (external checkout, store) trigger false alarms and stay out of this check. (Avoid: Accepting every billing expansion without checking for a charge.)
5. **An upsell closed in the CRM is expansion.** A won upsell deal counts as expansion in the forecast and in commissions, never toward the new-logo target. A deal in the new-business funnel for a company that is already a customer is misclassified expansion and goes to review. (Avoid: Letting upsell come in as a new sale.)
6. **Module attach over the eligible base.** Module attach rate = active customers with the module / active customers who can buy it (eligible plan and segment). Separate acquisition attach (module bought in the initial sale) from expansion attach (bought later). Eligible customers without the module and with related usage become the upsell list. (Avoid: Dividing by all customers, including those who can't buy it.)
7. **Charge items are validated by the total.** When a charge's extra amount is broken down by item, there may be no unique key and there may be legitimate identical rows, such as two items of the same value. Deduplicating throws away revenue. Validate by summing items against the charge total and create a non-itemized line for the difference, shown as a gap and never added to cross-sell. Refunds and deleted items stay out. (Avoid: Deduplicating identical charge item rows.)
8. **Repurchase timing from each company's cycle.** For recurring sales outside a subscription (packages, credits, one-off renewals), group the company's wins by day, compute the average interval between purchases over the last 6 to 12 months and flag repurchase timing when days since the last purchase exceed the average cycle minus a slack (for example, 7 to 14 days), with a declared floor such as 30 days. A company with a single purchase uses a declared default cycle. (Avoid: A single 90-day rule for every company.)
9. **Keep out of re-engagement who shouldn't get it.** Exclude from the expansion queue accounts that are inactive, in cancellation notice or delinquent, those served by their own channel (distributors, large accounts with a dedicated team) and those flagged with a restriction. The exclusion list is written and reviewed by the manager. (Avoid: Sending the whole base to the upsell queue.)
10. **Measure whether the queue works.** Store each entry into the repurchase or upsell queue with the date and the outcome: won, open, lost or no deal. The curve of wins by days after entry shows whether the timing is well calibrated and how long the team takes to act. (Avoid: Rewriting the queue every day without keeping history.)

### How each product movement is classified

By customer x product x month, comparing month end with the previous month end.

- **product already existed, its MRR went up** → Upsell (`end - start`)
- **new product, customer already paying** → Cross-sell (`product MRR at end`)
- **same customer, plan or cycle change** → Migration, outside natural expansion
- **canceled and re-signed within 30 days** → Migration, not churn plus new
- **renewal with the same quantity and price** → No movement
- **product MRR went down, customer still paying** → Product contraction (`start - end`)
- **product went to zero, customer keeps another product** → Product churn (`start`)

### Definitions

| Metric | Definition |
|---|---|
| Expansion MRR | sum of end - start over customer x product pairs where MRR went up, with the customer paying in both months, excluding migration |
| Cross-sell | month-end MRR of products the customer didn't have the previous month, while paying the previous month |
| Migration | MRR change from a cycle switch, renewal or re-sign by the same customer; its own column, outside natural expansion |
| Attach rate | active customers with the module / active customers eligible for the module, at month end |
| New revenue from the base | (expansion + cross-sell) / (expansion + cross-sell + new-logo MRR), per month |
| Average repurchase cycle | average days between consecutive purchase days of the company in the window; default cycle when there is a single purchase |
| Repurchase timing | days since the last purchase greater than or equal to the larger of the floor and the average cycle minus the slack |
| Post-timing conversion | companies that entered repurchase timing and won a deal within N days / companies that entered |

### Pitfalls

- **Expansion vanished in the snapshot.** The MRR table kept only the subscription's current state, and quantity increases on the same subscription never showed up as expansion. How to avoid: Record item change events or a daily snapshot.
- **A renewal zeroed out month end.** A filter that removed migration records zeroed the last day's MRR when the only record that day was a billing renewal, and the monthly view lost revenue every month. How to avoid: Sum the month snapshot without filtering migration; separate natural and migration only in the movements.
- **Deduplication threw away revenue.** Identical charge items were treated as duplicates. Compared with charge totals, reconciliation got worse: they were legitimate items of the same value. How to avoid: Validate by the sum against the total and expose the difference as non-itemized.
- **False alarm from an external channel.** The expansion-without-a-charge alarm flagged almost half the customers from an external checkout channel, which never creates an invoice in billing, against a few percent of the rest. How to avoid: Restrict the check to monthly plans in channels that create invoices.
- **Holding counted in every brand.** A contract closed at the holding showed up again in each subsidiary brand, and expansion by brand counted the same MRR more than once. How to avoid: Allocate the holding's MRR across brands by a written rule and skip subsidiaries with their own contract.
- **Weekly buyers flooded the queue.** With the cycle computed per deal, companies that closed several orders on the same day and bought every week entered repurchase timing almost every day. How to avoid: Group purchases by day and apply a floor to the cycle.

### Acceptance test

- [ ] For each month, the sum of product-lens movements equals the sum of the customer lens.
- [ ] No customer x product x month pair has more than one movement category.
- [ ] The sum of each charge's items, including the non-itemized line, matches the charge total.
- [ ] No company on the exclusion list appears in the repurchase or upsell queue.
- [ ] Migrations sit in their own column and don't go into natural expansion MRR.

### Context document

```text
Expansion and upsell
- Customer x product movement = product MRR at month end compared with the previous month end.
- Upsell = increase in a product the customer already had. Cross-sell = new product for a customer already paying.
- Migration (cycle switch, renewal, re-sign by the same customer) is its own column. Don't count it as expansion.
- A renewal with the same quantity and price is not a movement.
- An upsell won in the CRM is expansion, never new logo.
- Attach = active customers with the module / active customers eligible for the module.
- Charge items are not deduplicated. The difference to the charge total shows as non-itemized and doesn't go into cross-sell.
- Repurchase timing = days since last purchase >= max(<floor> days, average cycle - <slack> days), with purchases grouped by day.
- Out of the queue: inactive accounts, in cancellation notice, delinquent, own channel and restricted accounts.
Per-client exceptions:
- <client>: module <module> is only eligible on plans <plans>.
- <client>: segments <segments> don't go into re-engagement.
```

### Variations

- **Expansion forecast per account:** A calibrated model gives each account's upsell probability and expected MRR gain, and the top becomes the month's expansion queue.
- **Move to annual:** A dedicated queue of monthly customers with stable usage to offer the annual plan, measured separately from expansion.
- **Holding and brands:** Contract closed at the holding with usage by brand: allocate MRR by a written rule and don't count the same revenue in two brands.
