---
ref: workflow:cancellation-winback
title: Win back canceled customers with an offer for why they left
author: thedogwiththedataonit
tools: [tool:stripe/list-subscriptions, tool:typesafe/answer-typed-questions, tool:resend/send-email]
tags: [capability:classify-signals, capability:send-email, capability:track-revenue, channel:email, has:api, motion:retention]
updated: 2026-09-29
---

# Win back canceled customers with an offer for why they left

Reads why each Stripe subscription was canceled, sorts the reason with Jev, and emails a win-back offer that fits it with Resend.

Set up the tools below, then run the steps in order for the user, carrying each step's results into the next. The run is done when the user has the outcome below.

## Outcome

- A table of every recent cancellation with its reason, whether it leaves the door open, the offer picked and Jev's confidence.
- An approved win-back email sent to each customer who got an offer and opted in to marketing email.
- The customers left out, with why.

## Inputs

Ask the user for these before you start.

- `lookback_days`: how recent a cancellation counts, e.g. 14
- `offers`: the offer for each reason, with any promotion code already created in Stripe, e.g. too_expensive: 30% off for three months with the code COMEBACK30; low_usage: a free setup call; too_complex: a free setup call; missing_feature: a note when it ships; temporary_need: a free month when they return
- `comeback_link`: the page where a customer restarts their subscription, e.g. https://app.acme.com/billing
- `consent_source`: where you record who opted in to marketing email, e.g. a Stripe customer metadata field, or an export of opted-in addresses from your email tool
- `from_address`: the verified Resend sender the emails come from, e.g. Sam at Acme <sam@acme.com>
- `postal_address`: the mailing address every marketing email carries, e.g. Acme Inc., 1 Main St, Springfield, IL 62701
- `min_confidence`: how sure Jev must be before its answer is used without you, e.g. 0.8; a yes-or-no answer counts as yes at or above it and as no at or below 1 minus it

## Set up

### List subscriptions (Stripe, tool:stripe/list-subscriptions)

Use the first option your agent supports.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "stripe": { "url": "https://mcp.stripe.com" } } }
```

Call the MCP tool `stripe_api_read` with `GET /v1/subscriptions`.

#### CLI (official)

Install the command and sign in with it, then confirm it runs.

```sh
npm install -g @stripe/cli
stripe --version
```

Run `stripe subscriptions list`.

### Answer typed questions (TypeSafe AI, tool:typesafe/answer-typed-questions)

Use the API.

- Base URL: https://api.typesafe.ai
- Endpoint: `POST /v1/systemone`
- Auth: send the header `Authorization: Bearer $TYPESAFE_API_KEY`
- Get a key: https://console.typesafe.ai

Note: Put every question about one record in one request. `GET /v1/models` lists the models and is the cheapest check of a key; a request over the rate limit gets a 429 with a Retry-After header.

Note: Send `state`, `model` (`jev-latest`) and named `questions`, each with `type`, `instructions` and `criteria`: a choice maps labels to descriptions, a score lists its levels in order, a noul's is optional. Read a score by its most likely level; send unsure answers to a person.

### Send an email (Resend, tool:resend/send-email)

Use the first option your agent supports.

Note: Pass an `Idempotency-Key` header (`--idempotency-key` on the CLI) so a retry never sends it twice; keys expire after 24 hours.

#### MCP (official, remote)

Add this server to your agent's MCP settings, then sign in when asked.

```json
{ "mcpServers": { "resend": { "url": "https://mcp.resend.com/mcp" } } }
```

Call the MCP tool `send-email`.

#### CLI (official)

Install the command, then confirm it runs.

```sh
npm install -g resend-cli
resend --version
```

Run `resend emails send`.

Set `$RESEND_API_KEY` in your environment first (get a key: https://resend.com/api-keys).

Before step 1, confirm access to each service with its cheapest read-only call, like a list or a search. Never send or change anything to test access.

## Steps

1. **Pull cancellations** with List subscriptions (Stripe). List subscriptions with status `canceled`, each customer expanded, and page through them. Keep those whose `ended_at` falls in the last `lookback_days` and whose `cancellation_details.reason` is `cancellation_requested`, with each customer's name and email, the plan, how long they subscribed, and the cancellation feedback and comment; list the rest as left out.
2. **Sort the reasons** with Answer typed questions (TypeSafe AI). Send each cancellation's feedback, comment, plan and tenure as the state, with a choice `reason` of too_expensive (price or budget), missing_feature (needs something the product lacks), low_usage (did not use it enough), switched_to_competitor (moved to another product), too_complex (hard to set up or use), support_problem (unhappy with help received), temporary_need (a project or season ended), business_closed (the company closed or was acquired) and other, and a noul `door_open` (the comment names something that would bring them back). Keep each reason, its confidence and `door_open`.
3. **Pick the offers** yourself. Show the user every cancellation whose reason confidence is below `min_confidence` or whose `door_open` is unsure, and keep what the user decides. Give each customer the offer in `offers` for their reason. Leave out business_closed, switched_to_competitor unless `door_open` is yes, anyone with no offer for their reason and anyone `consent_source` doesn't show as opted in, and list each with why. Confirm the consent list with the user.
4. **Write the emails** yourself. For each customer, draft a subject line and three or four plain sentences: say what they told you in their own words, make the one offer for their reason, link to `comeback_link`, and end with a line to opt out of these emails and `postal_address`. Show the drafts to the user.
5. **Send** with Send an email (Resend). After the user approves, send each email from `from_address` to its customer, with an idempotency key per customer.

## Notes

Each offer answers one reason: a discount for price, a setup call for low usage or complexity. A customer who switched to a competitor is usually better left alone. Stripe's own feedback values are broad; the free-text comment is where the detail is, which is why Jev reads both. Customers whose subscriptions ended over a failed payment or a dispute need a billing fix, not an offer, so step 1 leaves them out.

Run it weekly with `lookback_days` set to 7.

## Rules

- Use only the services set up above. The read-only calls they need, like listing ids or polling for results, are fine.
- Ask the user before anything that sends messages, costs money, or changes data, and say how many records it touches. One approval covers a batch the user has seen.
- Never print API keys.
