---
name: stripe-churn-early-warning
description: "Mondays: paying accounts where a usage drop, a failed payment, an unhappy ticket or a cancel email line up, with the MRR at risk. Use when the user asks for this, or when it runs on a schedule."
---

# Churn early warning: Stripe, usage, support and email

Mondays: paying accounts where a usage drop, a failed payment, an unhappy ticket or a cancel email line up, with the MRR at risk.

One churn signal on its own is mostly noise. Usage dips in a holiday week, cards fail and get retried, customers open tickets because they use the product. Two at once is different: an account whose usage halved and who asked how to export their data is already shopping, and each tool shows you only half of it.

Every Monday the agent lines up active Stripe subscriptions with weekly usage, failed invoices, open or unhappy tickets and emails that mention cancelling, exports, refunds or alternatives. Accounts with two or more signals land on the card with their MRR, and the founder or support lead decides who writes a personal note this week.

## Two ways to get the data

Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.

## Route A: API or MCP server

### What you need

- Read access to Stripe Subscriptions, Customers and Invoices: the Stripe MCP server or a restricted key in `STRIPE_API_KEY`.
- A shared key from Stripe to the other tools: the Stripe customer ID stored as a PostHog group key, a Mixpanel profile property or an Intercom or Zendesk user field, or at least the same customer email everywhere.
- Read access to PostHog or Mixpanel, Intercom or Zendesk, and the Gmail inbox customers write to (`gmail.readonly`).

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **List paying accounts and failed payments** (Stripe): Page `GET /v1/subscriptions?status=active&expand[]=data.customer` and the same with `status=past_due`, and compute each customer's MRR: price times quantity per item, normalized to a month, after discounts. Then `GET /v1/invoices?status=open` and keep invoices from the last 30 days with `attempt_count` of 1 or more: payments that failed and are still being retried. Keep customer ID, email and email domain.
2. **Compare last week's usage with the 4 before** (PostHog): PostHog HogQL: `SELECT <account key> AS account, toStartOfWeek(timestamp, 1) AS week, count(DISTINCT person_id) AS users FROM events WHERE event = '<core event>' AND timestamp >= toStartOfWeek(now(), 1) - INTERVAL 5 WEEK AND timestamp < toStartOfWeek(now(), 1) GROUP BY account, week`, where the key is `$group_0` if your groups are keyed by Stripe customer ID, else the domain of `person.properties.email`. Mixpanel: the same weekly unique count of the core event broken down by the account property, through the MCP server's `Run-Query` or the segmentation endpoint with `type=unique` and `unit=week`. A drop is last week below half of the 4-week average, for accounts averaging 3 or more users.
3. **Collect open and unhappy tickets** (Intercom): Intercom: `POST /conversations/search` for conversations updated in the last 14 days that are `open` or `snoozed` for more than 3 days, or carry a `conversation_rating.score` of 1 or 2; the requester is `source.author.email`. Zendesk: `type:ticket updated>14days status<solved` through `/api/v2/search.json`, plus `GET /api/v2/satisfaction_ratings?score=bad` for the same 14 days.
4. **Search mail for leaving words** (Gmail): `users.messages.list` with `q` = `newer_than:14d -from:me -category:promotions {cancel cancellation refund "data export" "export our data" alternative "switch to" downgrade}`, reading the `From` address of each match. Anything not from a paying customer is dropped in the join.
5. **Join the signals per account**: Key every signal to a Stripe customer: by Stripe customer ID where the other tool stores it (a PostHog group key, a Mixpanel profile property, an Intercom or Zendesk user field), else by lowercased email, else by email domain for company accounts (never free-mail domains). Signals: usage drop, failed invoice, open or low-rated ticket, leaving-words email. Two or more is at risk; one goes on a watch-list count. Tickets and emails matching no customer are dropped; paying customers with no usage match are listed as "no usage data" so the key gets fixed.
6. **Ask who reaches out**: Post at-risk accounts by MRR with their signals, the MRR at risk and the watch-list count, and ask who should reach out. On the answer, draft a short personal note per account that deals with its signals (update the card, answer the ticket, offer help with the export). Nothing is sent.

## Route B: the browser

### What you need

- Signed in to the Stripe Dashboard (Analyst role or higher), to PostHog or Mixpanel, to Intercom or Zendesk, and to the Gmail inbox customers write to.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://dashboard.stripe.com/subscriptions. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **List paying accounts and failed payments**: In live mode, filter **Status** to **Active**, then **Past due**, and **Export** the list to read each customer's amount and email. Then open `https://dashboard.stripe.com/payments`, choose the **Failed** tab and note the customers with a failed payment in the last 30 days.
2. **Compare weekly usage**: PostHog: a new **Trends** insight on your core event counted as unique users, interval **Week**, last 5 weeks, broken down by the account (group or email domain). Mixpanel: an **Insights** report with the same settings. Flag accounts whose last full week is below half the average of the 4 before, among accounts averaging 3 or more users.
3. **Collect open and unhappy tickets**: Intercom: in the inbox, look at open and snoozed conversations older than 3 days and conversations rated 1 or 2 in the last 14 days. Zendesk: search `type:ticket updated>14days status<solved` and check bad satisfaction ratings in the same window.
4. **Search Gmail**: In `https://mail.google.com/` search `newer_than:14d -from:me -category:promotions {cancel cancellation refund "data export" alternative "switch to" downgrade}` and note each sender's email and domain.
5. **Line up the signals and ask**: Match every signal to a Stripe customer by email, then company domain (never free-mail). Two or more signals is at risk. Give the accounts by MRR with their signals, the MRR total and the count with one signal, then ask who should reach out. Send nothing.

Note: The usage breakdown in the UI only works if accounts are a group or a person property in your analytics; without that, the browser route can check usage for a short list of emails one by one. Stripe's Failed tab lists payments, so one customer can show several failed attempts for a single invoice.

## The report

- **At risk**: accounts with two or more signals, and the MRR they carry.
- **The list**, largest MRR first: each account's signals in plain words.
- **Watch list**: how many accounts show one signal.
- **Gaps**: paying customers the agent could not match to usage data.
- The ask: who reaches out, and drafts on request.
- What to do first: the largest account with a leaving-words email.

## Post it to Briefkin

When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.

```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
  -H "authorization: Bearer $BRIEFKIN_TOKEN" \
  -H "content-type: application/json" \
  -d @- <<'JSON'
{
  "v": 1,
  "event_id": "stripe-churn-early-warning/<date of this run, e.g. 2026-10-16>",
  "item_key": "stripe-churn-early-warning",
  "project": "Acme",
  "source": "Churn early warning: Stripe, usage, support and email skill",
  "kind": "action",
  "title": "5 accounts worth $1,340 MRR show 2+ churn signals",
  "summary": "**5 paying accounts ($1,340 MRR)** have two or more churn signals: 3 usage drops over 50%, 1 failed card with an open billing ticket, and 1 asked how to export their data. 9 more accounts show a single signal.",
  "action": {
    "request": "Who should reach out to these 5 accounts this week?",
    "options": [
      "Me, draft the notes",
      "Support lead, draft the notes",
      "Draft for the top 2 only",
      "Skip this week"
    ]
  },
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "Account",
          "MRR",
          "Signals"
        ],
        "align": [
          "left",
          "right",
          "left"
        ],
        "rows": [
          [
            "Contoso",
            "$420",
            "Usage -68%; ticket open 6 days: reports load slowly"
          ],
          [
            "Fourth Coffee",
            "$310",
            "Usage -74%; email: \"looking at alternatives before renewal\""
          ],
          [
            "Lamna Healthcare",
            "$240",
            "Card failed twice; billing ticket open"
          ],
          [
            "Tailspin Toys",
            "$190",
            "Usage -55%; rated a support conversation 1 of 5"
          ],
          [
            "Trey Research",
            "$180",
            "Email asking about a full data export; export ticket open"
          ]
        ]
      }
    },
    {
      "type": "Callout",
      "props": {
        "text": "Start with Fourth Coffee: weekly active users fell from 19 to 5, and their admin wrote on Thursday that they are looking at alternatives before renewal.",
        "tone": "bad"
      }
    }
  ]
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

## Run it on a schedule

Suggested: Mondays at 8:30 (`30 8 * * 1`). From cron, with the agent signed in on that machine:

```bash
30 8 * * 1 claude -p "Run the stripe-churn-early-warning skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```

Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/stripe-churn-early-warning#schedule

Source: https://briefkin.dev/skills/stripe-churn-early-warning
