AcmeChurn early warning: Stripe, usage, support and email · Claude Code
5 accounts worth $1,340 MRR show 2+ churn signals
Who should reach out to these 5 accounts this week?
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.
Account
MRR
Signals
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
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.
An example with made-up numbers. Your agent posts your real ones.
What it does
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.
How it works
1List paying accounts and failed payments
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.
2Compare last week's usage with the 4 before
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.
3Collect open and unhappy tickets
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.
4Search mail for leaving words
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
5Join 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
6Ask 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.
Post it to the board
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
What you get
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.
Install and run it
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
Agent
Data
Schedule
Install the skill
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shellcurl -fsSL https://briefkin.dev/skills/stripe-churn-early-warning/install | sh
Run it
Ask Claude Code:
promptRun the stripe-churn-early-warning skill
Or paste the prompt instead of installing:
promptFrom Stripe (read only), list active and past_due subscriptions with each customer's MRR (monthly, after discounts), email and domain, and open invoices from the last 30 days with at least one failed attempt. From PostHog or Mixpanel, get weekly unique users of my core event "<core event>" per account for the last 5 full weeks; flag accounts where last week is below half the average of the 4 weeks before (only accounts averaging 3+ users). From Intercom or Zendesk, get tickets from the last 14 days open more than 3 days or rated bad. Search my Gmail for the last 14 days for cancel, refund, data export, alternative, switch to or downgrade. Match everything to Stripe customers by customer ID, then lowercased email, then company email domain (not free-mail). Accounts with two or more signals are at risk: list them by MRR with their signals and the total MRR, give the count with one signal, and ask me who should reach out. Draft notes only when I say so; send nothing.
Put it on a schedule
Mondays at 8:30, from cron on any machine where Claude Code is signed in:
crontab30 8 * * 1 claude -p "Run the stripe-churn-early-warning skill"
Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.
Send it to your board
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a question for you. How setup works
SKILL.md---
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
What your agent needs
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).
Connecting Stripe
Stripe MCP server, Stripe's official remote server at https://mcp.stripe.com. Add Stripe from Claude's connector directory, or run claude mcp add --transport http stripe https://mcp.stripe.com/, and sign in with OAuth; clients without OAuth can send a restricted key as a Bearer token.
The Stripe CLI. Run stripe login once, then stripe subscriptions list --status active --limit 100 or stripe invoices list --status open. The CLI defaults to test mode, so add --live for real data.
API with a restricted key. Create a restricted key (rk_live_...) in the Dashboard under Developers > API keys, store it in STRIPE_API_KEY, and give it Read on the resources the report touches: Subscriptions, Invoices, Charges, Customers, PaymentIntents, Balance and Disputes.
Never hand an agent the secret key (sk_live_...); a restricted key with Read where needed and None everywhere else can't refund, cancel or charge anything.
Connecting PostHog
PostHog MCP server, PostHog's official remote server at https://mcp.posthog.com/mcp. Sign in with OAuth, or let the PostHog Wizard install it in your client; it routes to the US or EU cloud based on your account. Clients without OAuth can send a personal API key made with the MCP Server preset as a Bearer token (docs).
Query API with a personal API key. Create one in your PostHog account settings, store it in POSTHOG_API_KEY with POSTHOG_PROJECT_ID and POSTHOG_HOST (https://us.posthog.com or https://eu.posthog.com), and give it the query:read scope, plus insight:read or feature_flag:read if the report uses them.
Add ?readonly=true to the MCP URL (or send x-posthog-read-only: true) to drop every tool that creates, updates or deletes; on the API, read scopes alone do the same.
Connecting Mixpanel
Mixpanel MCP server, Mixpanel's official remote server at https://mcp.mixpanel.com/mcp (https://mcp-eu.mixpanel.com/mcp for EU, https://mcp-in.mixpanel.com/mcp for India). Mixpanel is in Claude's connector directory for US projects; for EU or India, add the regional URL as a custom connector. Its Run-Query tool runs insights, funnels, flows and retention queries (docs).
Query API with a service account. Create a service account in your organization settings, store MIXPANEL_SERVICE_ACCOUNT_USERNAME, MIXPANEL_SERVICE_ACCOUNT_SECRET and MIXPANEL_PROJECT_ID, and call the Query API with basic auth.
Give the service account access to only the project the report covers, with the Consumer or Analyst role rather than Admin.
Connecting Intercom
Intercom MCP server, Intercom's official remote server at https://mcp.intercom.com/mcp (https://mcp.eu.intercom.com/mcp for EU workspaces; Australian workspaces aren't supported yet). Intercom is in Claude's connector directory for US workspaces, and on Team and Enterprise plans an Owner adds it. Its tools search and fetch conversations and contacts (docs).
REST API with an access token. Create an app in the Intercom Developer Hub, store its access token in INTERCOM_ACCESS_TOKEN, and call https://api.intercom.io (api.eu.intercom.io or api.au.intercom.io by region) with an Intercom-Version header. Give the app Read conversations and Read and list users and companies.
The MCP server includes a few write tools (internal notes, Help Center articles), so for unattended reports an app token with read permissions only is the tighter option.
Connecting Zendesk
MCP server. Zendesk doesn't list an official one; community MCP servers exist, and they need the same subdomain, email and API token as a direct API call.
API with an API token. An admin enables token access in Admin Center under Apps and integrations > APIs and creates a token. The skill reads ZENDESK_SUBDOMAIN, ZENDESK_EMAIL and ZENDESK_API_TOKEN and sends basic auth as {email}/token:{token}.
OAuth client. For a narrower credential, register an OAuth client in Admin Center and request the read scope.
An API token acts as the user who made it, with that agent's full rights; an OAuth token with only read can't update, assign or close tickets.
Connecting Gmail
Claude's Gmail connector. In Claude, connect Gmail from the connector directory with your Google account to search and read your mail.
Gmail MCP server, Google's own remote server at https://gmailmcp.googleapis.com/mcp/v1, in developer preview with the other Google Workspace MCP servers. It needs an OAuth client in Google Cloud set up for an AI agent (setup).
Gmail API with OAuth. Create a desktop OAuth client, authorize once with the gmail.readonly scope, and store GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET and GMAIL_REFRESH_TOKEN. Publish the OAuth app (or make it Internal in Workspace): refresh tokens from an app left in Testing expire after seven days.
gmail.readonly can search and read but not send, delete or relabel; add gmail.compose only if you want the agent to leave drafts.
Questions
How does it match a Zendesk ticket to a Stripe account?
By Stripe customer ID when your support tool stores it on the user (many teams sync it into an Intercom attribute or a Zendesk user field), else by the requester's lowercased email, else by company email domain. A ticket from a teammate of the billing contact matches through the domain; one from a gmail.com address only matches on the exact email.
Why last week against a 4-week average, and why a 50% drop?
One week against one week swings with holidays and launches; four weeks smooths that out. Half is a drop people rarely reach by chance, and the minimum of 3 average users keeps a two-seat account with one person on leave off the list. Raise or lower both in the second step if your product is used monthly rather than weekly.
Do I need Briefkin to use the Churn early warning: Stripe, usage, support and email skill?
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a asks you, so you can glance at it instead of asking again.
Which agents can run it?
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Can it work without an API or MCP server, from my browser?
Yes. Switch the data source to "Your browser" and the steps change to the Stripe, PostHog, Mixpanel, Intercom, Zendesk and Gmail pages themselves: the agent opens dashboard.stripe.com/subscriptions in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.
Answer your agents from one board, or from Telegram
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.