Churn doesn't announce itself. My Monday Slack digest does it instead.
Every founder finds out about churn the same way: the cancellation email. By then the user has been gone for weeks - the decision happened earlier, quietly, in their usage. So I made the metering tables write me a memo.
The Monday digest
Every founder finds out about churn the same way: the cancellation email. By then the user has been gone for weeks - the cancel is the paperwork, not the decision. The decision happened earlier, quietly, in their usage: the daily user who went weekly, the power feature that stopped appearing, the API calls that flatlined. That decay is fully visible in metering data. The problem was never detection - it is that nobody reads per-user usage tables on a Monday morning. So I made the tables write me a memo. Vevee meters every user's AI usage (@vevee/sdk), and its compose() generates structured output from any user's history - including a churn-risk assessment. A Monday cron runs it over my paying users:
import { createClient } from "@vevee/sdk";
const vevee = createClient({ apiKey: process.env.VEVEE_SECRET_KEY! });
interface ChurnAssessment {
risk: "healthy" | "cooling" | "at_risk" | "gone_quiet";
evidence: string[]; // concrete usage facts, with numbers
probableCause: string; // best inference from the pattern
suggestedAction: string; // one thing, doable this week
}
const flagged: Array<{ email: string; a: ChurnAssessment }> = [];
for (const user of payingUsers) {
const result = await vevee.compose<ChurnAssessment>(
"churn-assessment",
user.id,
{ plan: user.plan, mrr: user.mrr }
);
if (result.status === "generated" && result.output.risk !== "healthy") {
flagged.push({ email: user.email, a: result.output });
}
}
await postToSlack("#health", formatDigest(flagged)); // sorted by MRR at riskWhat Monday looks like now
Here is what lands in #health. Two things make this readable where a dashboard is not. First, the comparison is to each user's own baseline - 50 events/week is healthy for one account and a 90% collapse for another; absolute thresholds cannot see that, the per-user composition can. Second, it arrives ranked and reasoned - not "engagement score: 42" but "she failed three times and stopped," which is a sentence a human acts on.
- #health - Week 24 · 3 accounts flagged · $94 MRR at risk
- dana@- (Pro, $15) - at_risk. Was 40 generations/week for 2 months → 4 last week → 0 this week. Last session ended on three failed style: "anime" requests in a row.
- Probable cause: hit a quality wall on her main use case, not loss of interest - frequency was stable until the failures.
- Action: personal email asking about the anime-style results; fix or workaround likely saves the account.
- team@- (Max, $49) - cooling. API volume -30% over 3 weeks; dashboard logins unchanged. Pattern fits their end-users dropping, not them leaving.
- Action: offer a 20-min call about their conversion funnel - their churn is your churn one step removed.
Close the loop or it's theater
Each flagged account gets the suggested action; the outcome is one analytics event away (same SDK, reserved event names). After a couple of months you know your save rate per risk tier and per action type - which converts "founder gut feel" into the retention playbook you will someday hand a hire.
await vevee.capture({ distinctId: userId, event: "feature_used",
properties: { source: "churn_save_action" } });
// vs. the eventual subscription_cancelled if the save failsCost and trust
A weekly sweep across a few hundred paying users costs cents - every result reports usage.costMicroUsd, and the dashboard's monthly compose budget hard-caps the spend. Users who opted out of AI personalization return opted_out and simply don't get assessed; for a consent-scoped system that's the correct trade, and you can still eyeball their raw charts by hand. The cancellation email is a lagging indicator with a 100% detection rate and a 0% save rate. The Monday digest is the opposite trade. Take it.
Keep reading
I rewrote my cancel flow with one LLM call. It argues better than I do.
The cancel flow is where SaaS revenue goes to die politely. "Are you sure? You'll lose access to Premium features" has never changed a single mind - but reminding a user of their own 312 generations this month is an argument.
ComposeThe trial-ending email everyone sends is the same email. Here's the one that converts
"Your trial ends in 3 days! Upgrade now to keep access." You've received a hundred of these. You've deleted a hundred of these. The email fails because it's about your product, not about the user's trial.