Back to Playbooks

Stop Waiting For Uninstalls: Predictive Churn Rescue For Shopify Apps

Free

Shows how a Shopify app like CartWizard wires behavior and billing signals into a churn_risk_score, then uses Spreeflo journeys to trigger predictive churn‑rescue flows so at‑risk merchants get timely automated and human intervention before uninstalling.

Industry

Niche

Pattern

Loading sequence...

By the time a Shopify merchant hits “Uninstall app,” you’re arguing with a decision they made weeks ago.

For most e‑commerce apps, churn looks “sudden” only because you’re not watching the right signals, or you only react when Shopify fires the uninstall webhook. The real story is gradual: config never finished, usage drifting down, invoices bouncing, support tickets getting sharper.

The sequence at the top of this page is the whole journey, end to end. It wires those weak signals into a churn‑risk score, then fires a rescue journey before merchants actually disengage.

In this playbook, we’ll use a fictional Shopify app, CartWizard, doing ~$100k MRR. Their biggest leak: installed stores that slowly stop using key features, then quietly churn. You’ll see how CartWizard uses Spreeflo to:

  • Maintain a numeric churn_risk_score for every store

  • Trigger a predictive rescue journey from a Criteria Match trigger when that score crosses a threshold

  • Combine automated emails with internal alerts so high‑value stores get human attention

Why reactive churn playbooks are already too late

Most app teams still treat churn as a binary event: installed vs uninstalled, active subscription vs canceled.

That shows up in automation as:

  • A “sorry to see you go” email when the uninstall event hits

  • Occasional “win back” campaigns to old users

Nothing in there protects the merchants who are about to churn but haven’t clicked uninstall yet. This is where most lifetime value leaks out.

For a Shopify or e‑commerce app, you already have better signals:

  • Core feature usage (feature_used, campaign_sent, order_synced)

  • Configuration completeness (has rules, templates, connected accounts)

  • Payment events (payment_failed, subscription_delinquent)

  • Logins and visits to your dashboard

  • Support tickets

The point of this pattern is to compress all that into a single churn‑risk score per contact, keep that score up to date, then let a Spreeflo journey watch for “score too high” and intervene.

That’s message #1 and #3 from the brand, in practice: capture detail on every customer so you can speak to each uniquely, and stop leaking lifetime value by failing to nurture engagement.

The architecture: score first, rescue second

Look back at the sequence at the top of this page and you’ll see two clusters of nodes:

  1. Small, event‑driven mini‑sequences that update a numeric churn_risk_score attribute using Update Contact Attribute actions.

  2. The main predictive rescue journey that starts when a Criteria Match trigger sees that score cross your danger line.

You don’t need a machine‑learning team for this. Start with a simple points‑based model:

  • Score starts at 0 (healthy)

  • Inactivity and problems add points

  • Strong usage subtracts points

Once that’s in place, the rescue journey is “just” a targeted lifecycle flow built in the campaigns and journeys editor.

Let’s walk through each part, node by node.

Step 1: Capture the signals that drive your score

Before you score anything, you need events.

For a Shopify app that lives mostly in a backend, most of your key events will be server‑side:

  • app_installed

  • app_uninstalled

  • feature_used

  • billing_failed

  • billing_succeeded

  • campaign_sent

  • support_ticket_opened

Those should be POSTed into Spreeflo via the Spreeflo API as custom events keyed by the merchant’s email (or another consistent contact identifier).

For browser‑level behavior in your app’s dashboard (page views, clicks, “visited pricing page”), you’d use the Spreeflo JavaScript SDK in your frontend to fire Spreeflo.page(...) and Spreeflo.track(...) events. That gives you Web Analytics and additional behavior for scoring, but the score model itself doesn’t care whether the event came from the SDK or the API.

You’ll also want a numeric contact attribute defined in your audience, for example:

  • churn_risk_score (NUMBER)

You create and manage attributes from the Audiences and attributes area, then reference them in journeys.

Step 2: Build a simple churn‑risk score with Update Contact Attribute

The journey’s left‑hand cluster in the sequence is just a set of tiny, event‑driven scoring flows. Each one listens for a Custom Event trigger and nudges churn_risk_score up or down using Update Contact Attribute.

Example 1: Daily inactivity penalty

  • Trigger: CYCLIC

  • Runs once per day.

  • criteria (using the segment builder):

  • Total Visits AT_MOST 0 in_the_last 7 days

  • AND Email Subscription Status IS Subscribed

  • AND Marketing Status IS Marketing.

  • isReEnrollment: true so contacts are processed on every daily tick they match.

  • Action: Update Contact Attribute

  • contactAttributeId: churn_risk_score

  • updateType: INCREMENT

  • newValue: 10

Every day a merchant is totally absent, their score goes up by 10.

Example 2: Core feature use lowers risk

  • Trigger: CUSTOM_EVENT

  • eventName: feature_used (your main value moment)

  • isReEnrollment: true so every usage counts.

  • Action: Update Contact Attribute

  • Attribute: churn_risk_score

  • updateType: DECREMENT

  • newValue: 15

Each time they use the app’s core feature, their risk score ticks down by 15.

Example 3: Payment issues spike risk

  • Trigger: CUSTOM_EVENT

  • eventName: payment_failed

  • isReEnrollment: true (recurring failures matter).

  • Action: Update Contact Attribute

  • Attribute: churn_risk_score

  • updateType: INCREMENT

  • newValue: 25

You can add more of these for your own signals: configuration incomplete, repeated 500 errors, etc. Just remember: each Update Contact Attribute node writes a fixed literal value or increment. If you want to bring in dynamic numbers (like amount from an event payload), that mapping needs to happen in your own backend before you upsert the attribute via the API.

With these three flows running, every merchant’s churn_risk_score naturally drifts up or down as their behavior changes.

Step 3: Criteria Match trigger when risk crosses your line

Now for the main rescue journey — the right‑hand cluster in the sequence.

We’ll start it with a Criteria Match trigger, which uses the same segment builder as segments but lives inside the journey.

Trigger: CRITERIA_MATCH

  • isReEnrollment: true

  • You want a merchant to be able to enter this rescue journey again in the future if their risk climbs back up, but Spreeflo will still prevent them from being enrolled twice at the same time.

  • criteria group:

  • Contact Attribute churn_risk_score GREATER_THAN 60

  • AND Email Subscription Status IS Subscribed

  • AND Marketing Status IS Marketing

  • AND (optional) Plan IS_NOT Trial

This says: “Any subscribed, marketing‑eligible paying merchant whose risk score is above 60 enters the journey.”

First action: tag them so you can see who’s in the funnel.

Action: ADD_TAG

  • Tags: ["at-risk"]

  • isForceTrigger: false (you don’t need to fire other journeys off this tag right now).

That tag is handy for filtering analytics, building ad‑hoc segments, or excluding contacts from other broadcasts while they’re being rescued.

Step 4: Route high‑value stores to humans first

Next in the sequence, you’ll see a fork that separates high‑value stores from everyone else so your team can step in early.

Process: IF_ELSE

  • condition (Segment Builder):

  • Contact Attribute plan_tier IS Pro

  • OR custom attribute mrr GREATER_THAN 200

Contacts who match go down the “then” branch (high value). Everyone else goes down “else”.

On the high‑value branch:

Action: SEND_INTERNAL_EMAIL

  • Template: “High‑risk merchant needs attention”

  • emailIdentityId: your usual sender (e.g. cs@cartwizard.app)

  • Include in the body: merchant’s store name, plan tier, current churn_risk_score, and the key events that pushed them into risk.

This is where a human can decide to jump into Intercom, send a personal Loom, or drop a Slack message to the account owner.

Both branches then feed into a MERGE node so everyone, regardless of value, rejoins the same core rescue cadence.

Step 5: Run a two‑email rescue cadence with built‑in behavior checks

From the MERGE, the journey moves into email — the best scalable channel you have for this moment.

Action: SEND_EMAIL (Rescue Email #1)

  • Built in the email builder

  • Subject: friendly and specific (“Quick check‑in on your CartWizard campaigns”)

  • Content:

  • Acknowledge they’ve been quieter than usual.

  • Offer 1–2 common fixes or tips based on your data.

  • Invite them to reply directly if something’s broken.

Configure:

  • isSendEmailOnlyOnce: true so they don’t see this twice if they re‑enter months later.

  • From an address that can accept replies (e.g. founder@ or success@), since replies are a strong “rescued” signal.

To avoid back‑to‑back sends, insert a delay:

Action: TIME_DELAY

  • unitCount: 3

  • unit: day

After three days, you want to see who actually reacted to that nudge.

Process: CHECK_EMAIL_ACTIVITY

  • templateId: the template for Rescue Email #1

  • emailActions:

  • One branch, e.g. {"clickedBranch": "clicked"}

  • elseBranchId: "noClickBranch"

Two paths:

  1. clickedBranch (they engaged meaningfully)

  2. noClickBranch (opened or ignored, but no click)

  • Action: UPDATE_CONTACT_ATTRIBUTE

  • Attribute: churn_risk_score

  • updateType: DECREMENT

  • newValue: 20

  • Action: REMOVE_TAG

  • Tags: ["at-risk"]

This path assumes click‑through is a strong enough “rescued” signal. The merchant has shown intent; your internal team already got notified if they were high‑value.

noClickBranch (opened or ignored, but no click)

These contacts need a second, clearer nudge.

  • Action: SEND_EMAIL (Rescue Email #2)

  • More direct subject (“Still worth keeping CartWizard installed?”)

  • Content focused on one strong benefit, plus an easy “book 15 minutes with us” link or a “pause my plan” option instead of uninstalling.

Remember, in this path the contact has already waited three days after Email #1, so you’re not violating any “back‑to‑back email” expectations on the subscriber side.

After Email #2, you again give them time and watch behavior:

  • Action: WAIT_CONDITION

  • condition:

  • Custom Event feature_used AT_LEAST 1 time in_the_last 4 days

  • unitCount: 4

  • unit: day

  • Process: IF_ELSE (“Did they come back?”)

  • Same condition group as the Wait Condition.

Two final outcomes:

a) Then branch (they re‑engaged)

  • Action: UPDATE_CONTACT_ATTRIBUTE

  • Attribute: churn_risk_score

  • updateType: DECREMENT

  • newValue: 30

  • Action: REMOVE_TAG

  • Tags: ["at-risk"]

  • (Optional) Action: ADD_TAG ["saved_by_predictive"] for later analysis.

b) Else branch (still cold after two emails and a week)

  • Action: ADD_TAG

  • Tags: ["likely-churn"]

  • Action: SEND_INTERNAL_EMAIL

  • Template: “Likely churn — decide next step”

  • Sent to a shared CS or growth inbox.

Some teams stop the journey here and let the uninstall event fire whenever it does. Others trigger a “last chance to stay on a free plan” offer from here. The point is that you now know who is truly at risk, not just who is quiet.

Step 6: Measure retention lift and ROI

You built this to reduce churn, not to send more email. Without measurement, you’re guessing.

At a minimum:

  • Use tags like at-risk, saved_by_predictive, and likely-churn to build reporting segments.

  • Compare churn rates over 30–60 days between:

  • Merchants who hit churn_risk_score > 60 and went through the rescue journey

  • Merchants who never hit that threshold

If you want a cleaner read on lift, add a Random Split node right after the ADD_TAG in the rescue journey:

  • RANDOM_SPLIT

  • percentageWeight: 0.9 (90% to treatment, 10% to holdout)

  • aBranchId: treatment

  • bBranchId: control

Send the control branch to a terminal node (no rescue emails), maybe just keeping the at-risk tag for analysis. Everyone in treatment continues through the rescue cadence.

After a couple of months, compare:

  • Churn rate in the treatment cohort vs control

  • Average extra months of revenue for merchants tagged saved_by_predictive

Even a small relative reduction in churn on your at‑risk cohort usually repays what you pay for automation many times over — especially if you’re on a plan that distinguishes between marketing and non‑marketing contacts the way Spreeflo’s pricing does.

Tuning over time: smarter scores, sharper messaging

Once the basic pattern is live, you can iterate on two axes:

  1. The score logic

  2. The messaging

  • Increase penalties that correlate strongly with actual churn.

  • Add positive signals (successful campaigns, repeat feature use) that bring scores down faster.

  • If you ever introduce a more advanced model in your own backend, you can overwrite churn_risk_score directly via the API and keep the same rescue journey.

  • Use the rescue tags to pull export lists and look at qualitative differences.

  • Test alternative copy in Rescue Email #1 via A/B testing in separate journeys.

  • Adjust timings: some apps find 7 days of inactivity is enough to trigger risk; others push it to 21.

Because all of this sits in one automation canvas, you’re not gluing together five tools. You’re capturing detailed behavior, aggregating it into a simple score, and reacting with personalised, context‑aware messaging that treats each store according to its risk and value.

The quiet compounding of predictive rescue

A single saved $99/month store doesn’t move your MRR graph. But saving a handful every month adds up quietly: shorter payback on acquisition, higher LTV, less pressure to keep filling the top of the funnel.

That’s where founder‑led teams like CartWizard win. They don’t add headcount to watch dashboards all day. They design a system once in Spreeflo’s journeys, wire in the right signals, and let it run.

Capturing fine‑grained detail on every merchant — then using that detail to talk to each one uniquely before they disappear — is the difference between reactive churn emails and a retention engine that earns its keep every month.