Stop the leak at the cancel button: a churn‑risk save flow for Shopify apps
Shows Shopify app teams how to build a churn‑risk save flow in Spreeflo, catching cancel‑page visits and at‑risk tags, splitting high‑value vs standard accounts, and tagging outcomes so you can measure saves and protect MRR automatically.
Industry
Niche
Pattern
Loading sequence...
The founder of “CartPilot” thought they had churn under control.
Installs were steady, reviews were good, and the app’s Shopify App Store listing kept climbing. But MRR wasn’t moving. When they finally pulled a cohort report from their billing system, the pattern was brutal: a long tail of customers quietly quitting within 60–90 days. Most of them had opened the billing or cancel page at least once before they left.
They weren’t churning out of the blue. They were sending a flare.
The sequence at the top of this page is the whole journey, end to end. It’s a churn‑risk save flow built for exactly this moment: the few hours or days when a merchant is hovering over “uninstall” or flagged as at‑risk by your health model.
In this article, we’ll walk that journey node by node. You’ll see how to catch cancellation intent in real time, send a tailored save offer, and route high‑value accounts to a human before they disappear — all inside one journey built in the campaigns and journeys editor.
We’ll use a fictional Shopify app, “CartPilot,” doing about $80k MRR with a 7% monthly churn rate. Their goals:
Catch merchants who hit the cancel page or get tagged as at‑risk.
Offer something meaningful (discount, concierge setup, feature guidance) based on who they are.
Get a human in the loop for the top slice of revenue.
What this churn‑risk save flow actually does
Before we go node‑by‑node, zoom out:
Behavioural signals (visiting your cancel page, or being tagged “at‑risk”) enroll a contact.
The journey records why they’re at risk so you can analyze later.
High‑value accounts trigger an immediate internal alert and a richer save offer.
Everyone else gets a well‑timed, single save email.
A Wait Condition watches for an actual cancellation event over a few days.
At the end, contacts are tagged as “churned” or “saved,” so you can measure save rate and post‑save retention.
This is how you stop treating “uninstall” as a binary and start treating cancellation intent as its own lifecycle stage.
Now let’s walk through the sequence at the top of this page.
Two behavioural triggers into the same journey
CartPilot has two clear churn‑risk signals:
A merchant visits the in‑app cancel page.
A separate health model tags them as “at‑risk” based on low usage.
We model each as its own journey entry.
Trigger 1: Custom Event for cancel page visits
The first trigger is a Custom Event:
Event name:
cancel_page_viewed(you name this in your app).Re‑enrollment: on.
You send this event from your app backend or frontend using the Spreeflo API or the JS SDK whenever a logged‑in merchant lands on your cancel page.
Re‑enrollment is turned on because the same store might open the cancel page several times over its lifetime. You want the save flow to run again if they come back six months later, not just the first time they thought about quitting. Spreeflo will still enforce the mid‑journey lock: if they’re already inside this churn‑save journey, a new event won’t create a duplicate enrollment.
Immediately after this trigger, we place an Add Tag action:
Tag:
churn-intent:cancel-page.
This doesn’t affect behavior yet; it’s there to capture detail on every customer so you can speak to each uniquely later. Over time, you’ll know how many “almost cancelled” contacts came from explicit cancel intent versus a health score.
Trigger 2: Added Tag for health‑score risk
The second entry point is an Added Tag trigger:
Tag:
at-risk.Re‑enrollment: on.
Your own health‑scoring system can apply this tag via API or import when usage drops below a threshold: no sessions in two weeks, key feature not used, NPS of 5, etc.
Right after this trigger, we mirror the same pattern with an Add Tag action:
Tag:
churn-intent:health-score.
Now you’re not just reacting to risk. You’re recording what kind of risk it is, so your messaging and reporting stay sharp.
Merging both paths
Both trigger arms feed into a Merge node.
The Merge is important: Spreeflo’s journeys are single‑entry per contact at any given time, and only Merge is allowed to have multiple incoming paths. By merging, you keep the downstream logic — save offer, wait, outcome tagging — in one place, regardless of whether risk came from behaviour or from your model.
First safety check: are they already cancelled?
From the Merge, we branch with an If/Else process: “Is this subscription actually still active?”
You don’t want to email a save offer to someone who already uninstalled or cancelled billing through Shopify’s UI. CartPilot keeps a subscription_status attribute in sync from their billing system via the Spreeflo API. In the If/Else condition, they configure:
Condition group: AND
Rule: Contact Attribute
subscription_statusisactive.
Two branches:
“Still active” (condition true).
“Already cancelled” (else).
On the “already cancelled” side, the journey can be very light:
Optional Add Tag:
churned:detected-post-hoc.Optional Send Internal Email to a shared inbox so success can sanity‑check why the automation saw cancellation intent late.
Most contacts on this branch just exit. You avoid sending confusing emails to people who consider the relationship already over.
Everyone else — the ones you can still save — move down the “still active” path.
Splitting high‑value vs standard accounts
Not every account deserves the same level of human attention. CartPilot defines “high value” as any store paying more than $150/month or on the “Plus” plan.
Right after the “still active” branch, we add a Multi-way Split to separate high‑value accounts from the rest.
Inside the Multi-way Split:
Branch “High value”: Condition: Contact Attribute
mrrgreater than 150 OR Contact Attributeplan_tierisPlus.Else branch: everyone else.
You can define this logic in one group using the segment builder syntax: an AND/OR combination of numeric and text attributes.
This single fork is what turns a generic save flow into something that actually respects account value and lifetime upside.
The human touch path for high‑value accounts
On the “High value” branch, CartPilot wants a person involved quickly, without giving up automation.
Step 1: Internal alert with context
The first node here is Send Internal Email to success@cartpilot.app:
Subject: “High‑MRR account at churn risk: {{store_name}}”.
Body: include key contact attributes:
mrr,plan_tier,installed_at, last feature used, and which tag triggered the journey (churn-intent:cancel-pageorchurn-intent:health-score).
Because this node targets your team, not the merchant, it doesn’t count toward the “don’t over‑message the contact” rule.
The point is simple: your tiny team learns about at‑risk VIPs while there’s still time to do something.
Step 2: Give them a breather, then send the save email
Next, add a Time Delay:
Duration: 1 hour.
Opening your cancel page and receiving an email two minutes later feels creepy. An hour respects the user’s agency and gives your internal team time to scan the alert if they want to act manually.
After the delay, we place the first Send Email node: the actual save offer.
CartPilot designs this template in the email builder:
From: a real person (e.g., “Maya from CartPilot”).
Subject line: empathetic, not needy.\nExample: “Quick check‑in before you leave CartPilot”.
Body:\n- Acknowledge they might be considering uninstalling.\n- Ask the key question: “What would make CartPilot worth keeping for you?”\n- Offer something specific: a 30‑minute 1:1 optimization call or a month at a lower tier, depending on plan.\n- Include a simple reply CTA and a clear “Manage subscription” link so you’re not holding them hostage.
Because Spreeflo keeps all your contact attributes on one record, you can reference their plan, install date, or usage in this email to keep it personal without hand‑writing each one.
Step 3: Wait to see what they actually do
We now want to observe behaviour for a few days.
Add a Wait Condition:
Condition: Custom Event
subscription_canceledOperator: at least 1 time
Time window: in the last 3 days.
Timeout: 3 days.
Your billing system should fire the subscription_canceled custom event any time a store’s subscription is actually turned off.
Here’s how this Wait Condition behaves:
If
subscription_canceledfires within 3 days, the contact moves on as soon as the event arrives.If nothing happens, they move on automatically when the 3‑day timeout expires.
Either way, the next node runs soon after something meaningful happens or enough time has passed to call it a “save.”
Step 4: Branch on outcome — cancelled vs saved
Right after the Wait Condition, we use another If/Else:
Condition: Custom Event
subscription_canceledOperator: at least 1 time
Time window: in the last 3 days.
If it’s true, they’ve cancelled. If not, they’re still with you.
On the “Cancelled” branch:
Add Tag:
churned.Update Contact Attribute: set
churned_attimestamp to now (using the “Set to now” update type).Send Internal Email: short note to your team so they can log feedback or reach out manually if appropriate.
On the “Saved” branch:
Add Tag:
saved-from-churn-flow.Update Contact Attribute: set
last_churn_save_atto now.
Optionally, after a 1‑day Time Delay, you can send a short Send Email:
Subject: “Thanks for sticking with CartPilot”.
Body: reinforce the value they’re getting and link to a quick “get more from CartPilot” guide.
Because there is at least a Wait Condition or Time Delay between every pair of Send Email nodes, you stay within healthy pacing.
The lightweight automated path for everyone else
For standard‑value accounts, you still want to offer a thoughtful save experience — just without the manual overhead.
On the Multi‑way Split’s “Else” branch (non‑high‑value):
Time Delay of 1 hour. Same rationale: don’t pounce instantly.
Send Email with a simpler save offer:\n- Recognize they might be about to cancel.\n- Ask a single question about why: price, missing feature, too complex, wrong fit.\n- Offer either:\n - A stripped‑down plan, or\n - A link to a tailored guide based on usage (e.g., “Getting first results in 24 hours”).
You can even pass the cancel reason into a
cancel_reasoncustom attribute from your app, and use conditional sections in the email body to speak directly to “too expensive” vs “not seeing value.”Wait Condition: same as the high‑value path, watching for
subscription_canceledfor up to 3 days.If/Else on that custom event to separate “Cancelled” and “Saved,” then:\n\n- Cancelled:\n - Add Tag
churned.\n - Optionally, Add Tagchurned-from-save-flowto distinguish them from people who left without ever hitting this journey.\n- Saved:\n - Add Tagsaved-from-churn-flow.\n - Optional thank‑you Send Email after a 1‑day Time Delay, reinforcing how to get quick wins.
Again, there’s always a Time Delay or Wait Condition between every pair of contact emails.
This entire branch runs with zero manual work yet treats churn‑risk as a real moment, not just another monthly invoice that either clears or doesn’t.
Why all the tags and attributes matter later
At this point, you’re not just trying to save accounts; you’re building a rich dataset about your near‑churn population.
From this one journey, each contact might accumulate:
churn-intent:cancel-pageorchurn-intent:health-scorechurnedvssaved-from-churn-flowlast_churn_save_attimestampSegments built on MRR, plan tier, or cancel reason
With that, you can:
Build a “Saved but still at risk” audience using the segment builder and send a quarterly “power tips” campaign.
Compare post‑save retention vs overall retention, using tags to filter cohorts.
Identify features that correlate with successful saves by comparing usage before and after
last_churn_save_at.
This is Brand Message 1 in action: capture detail on every customer so you can speak to each uniquely. A save email that speaks to “too expensive” reads very differently from one that speaks to “not seeing value,” and Spreeflo gives you the rails to express that nuance without manual work.
Measuring whether this journey is actually working
Three metrics matter for this pattern:
Save rate:\nAmong contacts who enter the journey, what percentage end up tagged
saved-from-churn-flowafter 3–7 days?Cancellation rate:\nWithin that same cohort, how many end up tagged
churnedinstead?Post‑save retention:\nOf the
saved-from-churn-flowgroup, how many are still active 60 or 90 days later?
You can track these by creating simple saved segments:
Segment A:
saved-from-churn-flowANDsubscription_statusisactive.Segment B:
churnedANDchurn-intent:*(meaning they showed risk first).
Once those segments exist, you can compare counts over time or even send targeted follow‑ups. For example, a “we added the feature you requested” campaign to everyone with churned and cancel_reason = missing feature.
If you’re sending a lot of volume, step up to the Professional plan and combine this with web tracking from the web tracking and analytics docs: see whether “saved” users actually come back to key features, not just keep paying.
The bigger point: most businesses are leaving lifetime value on the table because they don’t nurture engagement at the edge of churn. If you do nothing, every cancel click is a hard exit. With this flow, every cancel click is a conversation starter.
Adapting this pattern to your app
Every e‑commerce app is different, but the spine of this journey is reusable:
Swap CartPilot’s events for your own
app_uninstalled,billing_page_viewed, orplan_downgradedevents.Define “high value” your way: based on GMV processed, orders touched, or number of seats.
Adjust timing: for high‑urgency tools (fraud, shipping), you might shorten the Wait Condition to 24 hours; for slower‑burn analytics, 5–7 days could be right.
The key is to respect two constraints:
Message pacing: every path in your journey should have a Time Delay or Wait Condition between every pair of Send Email nodes.
Re‑enrollment: for churn‑risk, leave it on for both the Custom Event and Added Tag triggers so that if someone comes back to the brink months later, they get the right experience again.
Once you build this once, it doesn’t ask much of you. A small founder‑led team gets a churn‑save motion that behaves like a mature CS operation.
You stop playing whack‑a‑mole in your inbox with “Can we keep you?” emails. Instead, you have a clear, data‑rich system: one that catches the flares, speaks differently to each customer, and quietly protects MRR in the background.
That’s the real win here: not a magic anti‑churn email, but a habit of treating cancellation intent as a stage worth designing for — and a tool that makes that design automatic.