Flag churn risk in new support tickets for customer success
Reads new Zendesk tickets, scores churn risk and urgency with Jev, tags and prioritizes the risky ones, and alerts each owner in Slack.
Outcome
A table of every new ticket with its churn-risk level, issue type, urgency and Jev's confidence.
Each at-risk ticket tagged, reprioritized and given an internal note in Zendesk.
A Slack post for each at-risk ticket that mentions its HubSpot owner and carries a drafted check-in.
How it works
- 1
Pull new tickets
ZendeskSearch for tickets (
type:ticket) created aftersince, leaving out those already taggedrisk_tag. Keep each ticket's ID, subject, description, priority, tags and requester ID. - 2
Read each ticket
TypeSafe AISend the subject and description as the state, with a score
churn_riskon four levels (no risk; frustrated; weighing a downgrade or another tool; says they will cancel), a choiceissueof bug, billing, how_to, missing_feature, performance, access, cancellation and other, a scoreurgencyon three levels (can wait; this week; today), a noulmentions_competitorand a noulasks_for_refund. Keep every answer with its confidence or probability. - 3
Check the unsure ones with the user
Your agentShow the user every ticket whose
churn_riskconfidence is belowmin_confidence, and keep the level the user picks. Keep the tickets whose most likelychurn_risklevel isrisk_levelor higher, and look up each one's requester email, a read-only call. - 4
Find the owner
HubSpotSearch companies by each at-risk requester's email domain, skipping free email domains. Keep the company's record ID, name and
hubspot_owner_id, and look up each owner's name and email, a read-only call. Note the tickets with no match. - 5
Mark the ticket
ZendeskAfter the user approves, add
risk_tagalongside each at-risk ticket's existing tags, raise its priority to high, or urgent when its urgency is today, and add an internal note, not a public reply, with the churn-risk level, the issue and the competitor and refund probabilities. - 6
Draft the check-in
Your agentWrite a short note from each company's owner, by name, that names the problem in the ticket, says what happens next and offers a call, leaving a [fill in] for any fix date or commitment you don't have. Show the drafts to the user.
- 7
Find the owners in Slack
SlackLook up each owner's email. Keep their Slack user ID.
- 8
Alert the owner
SlackPost each at-risk ticket to
cs_channel, mentioning its owner, with the company, the churn-risk level, the issue, the competitor and refund probabilities, the ticket ID and the drafted check-in.
You'll be asked for
When the last run started, as a time, so only newer tickets are read
e.g. 2026-09-28T09:00:00Z
The lowest churn-risk level that counts as at risk
e.g. weighing a downgrade or another tool
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
The Zendesk tag that marks an at-risk ticket
e.g. churn_risk
The Slack channel customer success watches
e.g. #cs-alerts
The file your agent runs
support-ticket-churn-risk.md
Flag churn risk in new support tickets for customer success
Reads new Zendesk tickets, scores churn risk and urgency with Jev, tags and prioritizes the risky ones, and alerts each owner in Slack.
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 new ticket with its churn-risk level, issue type, urgency and Jev's confidence.
- Each at-risk ticket tagged, reprioritized and given an internal note in Zendesk.
- A Slack post for each at-risk ticket that mentions its HubSpot owner and carries a drafted check-in.
Inputs
Ask the user for these before you start.
since: when the last run started, as a time, so only newer tickets are read, e.g. 2026-09-28T09:00:00Zrisk_level: the lowest churn-risk level that counts as at risk, e.g. weighing a downgrade or another toolmin_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 itrisk_tag: the Zendesk tag that marks an at-risk ticket, e.g. churn_riskcs_channel: the Slack channel customer success watches, e.g. #cs-alerts
Set up
Zendesk (tool:zendesk/search, tool:zendesk/update-ticket)
Use the API.
- Base URL: https://{subdomain}.zendesk.com
- Search tickets, users and organizations:
GET /api/v2/search?query={query} - Update a ticket:
PUT /api/v2/tickets/{ticket_id} - Auth: send the header
Authorization: Bearer $ZENDESK_OAUTH_TOKEN - Get a key: https://developer.zendesk.com/documentation/authentication/oauth-migration/#getting-your-first-token
Note: {subdomain} is your Zendesk subdomain, as in acme.zendesk.com. Tokens from OAuth clients created since April 30, 2026 expire after 30 minutes.
Notes:
- Search tickets, users and organizations: Returns at most 1,000 results per query and 100 per page; for more, use
GET /api/v2/search/export. - Update a ticket: A
commentwithpublic: trueis a reply the requester can see;public: falsemakes it an internal note. This endpoint has its own rate limit and returns 429 when it runs out.
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.
Search CRM records (HubSpot, tool:hubspot/search-crm-records)
Use the first option your agent supports.
Note: The MCP tool takes up to five groups of six filters and returns up to 200 records per page.
MCP (official, remote)
Add this server to your agent's MCP settings, then sign in when asked.
{ "mcpServers": { "hubspot": { "url": "https://mcp.hubspot.com" } } }
Call the MCP tool search_crm_objects.
API (official)
- Base URL: https://api.hubapi.com
- Endpoint:
POST /crm/objects/2026-09/{objectType}/search - Auth: send the header
Authorization: Bearer $HUBSPOT_API_KEY
Note: Use a service key or an app's static access token with the CRM scopes the calls need; paths carry a dated version such as 2026-09.
Slack (tool:slack/find-user-by-email, tool:slack/post-message)
Use the first option your agent supports.
Notes:
- Find a user by email: Needs the
users:read.emailscope; a deactivated user returnsusers_not_found. - Post a message: Needs the
chat:writescope. Over MCP it posts as the signed-in user; the API and CLI post as the app's bot.
MCP (official, remote)
Add this server to your agent's MCP settings, then sign in when asked.
{ "mcpServers": { "slack": { "url": "https://mcp.slack.com/mcp" } } }
- Find a user by email: call the MCP tool
slack_search_users - Post a message: call the MCP tool
slack_send_message
CLI (official)
Install the command, then confirm it runs.
curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash
slack --version
- Find a user by email: run
slack api users.lookupByEmail - Post a message: run
slack api chat.postMessage
Set $SLACK_BOT_TOKEN in your environment first (get a key: https://api.slack.com/apps).
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
- Pull new tickets with Search tickets, users and organizations (Zendesk). Search for tickets (
type:ticket) created aftersince, leaving out those already taggedrisk_tag. Keep each ticket's ID, subject, description, priority, tags and requester ID. - Read each ticket with Answer typed questions (TypeSafe AI). Send the subject and description as the state, with a score
churn_riskon four levels (no risk; frustrated; weighing a downgrade or another tool; says they will cancel), a choiceissueof bug, billing, how_to, missing_feature, performance, access, cancellation and other, a scoreurgencyon three levels (can wait; this week; today), a noulmentions_competitorand a noulasks_for_refund. Keep every answer with its confidence or probability. - Check the unsure ones with the user yourself. Show the user every ticket whose
churn_riskconfidence is belowmin_confidence, and keep the level the user picks. Keep the tickets whose most likelychurn_risklevel isrisk_levelor higher, and look up each one's requester email, a read-only call. - Find the owner with Search CRM records (HubSpot). Search companies by each at-risk requester's email domain, skipping free email domains. Keep the company's record ID, name and
hubspot_owner_id, and look up each owner's name and email, a read-only call. Note the tickets with no match. - Mark the ticket with Update a ticket (Zendesk). After the user approves, add
risk_tagalongside each at-risk ticket's existing tags, raise its priority to high, or urgent when its urgency is today, and add an internal note, not a public reply, with the churn-risk level, the issue and the competitor and refund probabilities. - Draft the check-in yourself. Write a short note from each company's owner, by name, that names the problem in the ticket, says what happens next and offers a call, leaving a [fill in] for any fix date or commitment you don't have. Show the drafts to the user.
- Find the owners in Slack with Find a user by email (Slack). Look up each owner's email. Keep their Slack user ID.
- Alert the owner with Post a message (Slack). Post each at-risk ticket to
cs_channel, mentioning its owner, with the company, the churn-risk level, the issue, the competitor and refund probabilities, the ticket ID and the drafted check-in.
Notes
Judge risk by the most likely level, not the expected score. Start with risk_level at weighing a downgrade or another tool, and move it once you have a week of results. Support keeps answering the ticket as usual; the account owner hears about it the same day.
Run it every few hours with since set to the time the previous run started.
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.
