Back to Playbooks

The “last 3 days” trial sequence that quietly saves your Shopify app MRR

Free

Shows how to build a Spreeflo journey that targets Shopify app trials in their final three days, splitting activated and dormant stores, escalating urgency and internal alerts so quiet trial churn turns into more predictable, compounding MRR.

Industry

Niche

Pattern

Loading sequence...

The worst kind of churn isn’t angry.

It’s the quiet kind: the store installs your app, runs a 14‑day trial, pokes around a bit, then… nothing. No downgrade email. No “you’re missing X.” Stripe just never sees a charge. Your MRR graph shrugs, and that trial disappears into the void.

If you check the logs, the story looks familiar:

  • Trial created on Day 0.

  • A few feature_used events scattered across the first week.

  • Maybe a spike in page_viewed:/pricing on Day 10.

  • Trial expired on Day 14 with no card on file.

You might already send a reminder: “Your trial ends today.” That’s better than nothing. But if that’s your only move, you’re leaving a lot of lifetime value on the table. The final 3 days of a trial are your highest‑intent window. Treating them like any other week is a waste.

The sequence at the top of this page is the whole journey, end to end. It kicks in only in the final stretch of the trial, fans out based on whether the store is activated or dormant, escalates your prompts, and pulls your team in when it actually matters.

We’ll walk it using a fictional app, then break down every node so you can adapt it to your own product.

Our example: UpsellPilot, a Shopify upsell/cross‑sell app doing $80k MRR. They have a 14‑day trial, charge $39–$249/mo after that, and track key events like trial_started, app_installed, upsell_widget_live, order_with_upsell, and billing_updated into Spreeflo via the SDK and a small backend worker. Their problem: 40–50% of trials go all the way to Day 14 without ever adding a card.

This pattern is how they turned that leaky window into predictable revenue.

Why the last 3 days matter more than the first 3

Think about your own behavior with SaaS trials.

You install. You kick the tires. Then life happens. When do you seriously consider paying? Not on Day 2. It’s when:

  • You’ve seen at least a bit of value.

  • The app reminds you that the “free” period is ending.

  • The cost of losing that value suddenly feels real.

UpsellPilot realized two things looking at their data:

  • Most conversions clustered in the last 4 days of trial.

  • Their “trial ending” email went to everyone, regardless of whether they’d seen value.

Same subject line. Same call‑to‑action. No distinction between a store that already generated $600 in upsell revenue and one that never went live.

The fix isn’t complicated: create a dedicated “final 3 days” journey that:

  1. Knows when a trial is about to expire.

  2. Checks whether the store is activated (using your own events).

  3. Sends different messages to each group, with different levels of urgency.

  4. Pings you internally when a high‑value but undecided store reaches the cliff edge.

That’s exactly what the sequence at the top of this page does.

Step 1: Target only the right trialists with a Criteria Match trigger

This is not a generic weekly blast. You want a tight slice:

  • Contacts who are on an active trial.

  • Whose trial end date is in 3 days.

  • Who haven’t already converted or canceled.

UpsellPilot tracks trial_end_at as a timestamp attribute on each contact. Once a day, they let Spreeflo pull in just the people hitting the “3 days left” mark.

In the journey, the entry point is a Criteria Match trigger:

Criteria:

  • Contact attribute trial_status is active.

  • Contact attribute trial_end_at is within the next 3 days.

  • Contact attribute is_paid is false (or missing).

Re‑enrollment: off. You don’t want the same contact entering this sequence multiple times.

Under the hood, this uses the same segment builder UI you’d use for a saved audience. The difference is you’re defining it inline on the trigger, not as a global segment. If you’d rather reuse it elsewhere (“all trials ending soon”), you could build it once as a segment in the audiences view and use a Join Segment trigger instead.

Why date‑based, not event‑based? Because “3 days to go” is about a clock, not a specific user action. Criteria Match with timestamp logic makes that easy without touching code.

Step 2: Snapshot their activation state with tags and attributes

The next thing the journey needs to know is: has this store actually used the app?

UpsellPilot defines “activated” as:

  • At least one upsell widget is live.

  • At least one order has fired the order_with_upsell event.

They maintain a boolean attribute is_activated server‑side. Whenever they see order_with_upsell from a new store, they set it to true via the Spreeflo API. You could also derive this directly from tracked events in the journey with a Wait Condition and an Update Contact Attribute node, but precomputing it keeps the flow lean.

As the first action after the trigger, they normalize state:

Add Tag:

  • trial-expiring-3d

Remove Tag:

  • trial-expiring-1d (in case they change dates manually and re‑enter another flow later).

Tags here act as lifecycle flags. They make it easy to exclude “expiring now” trials from other campaigns so you’re not overlapping messages.

Configuration:

  • One Add Tag node (trial-expiring-3d), “Force tag trigger” off.

  • One Remove Tag node (trial-expiring-1d), “Force tag trigger” off.

You could skip this, but giving your trials clear lifecycle labels pays off later when you build other campaigns and journeys.

Step 3: Branch the journey by activation: If/Else on your own usage signal

Now you split.

Add an If/Else process node right after the tagging step:

  • Condition: Contact attribute is_activated is true.

  • Then branch: “Activated”.

  • Else branch: “Dormant”.

Why not branch on email engagement? Because in this pattern, activation is about product usage, not opens. Email behavior matters later, but your main fork should follow what they did in your app.

This is also where you credit yourself for decent event tracking. If you’ve wired up upsell_widget_live, feature_used, or order_with_upsell, turning that into a simple boolean attribute is trivial. If not, the sequence at the top of this page is a reminder to send that data into your workspace via the web SDK or API so you can do smarter lifecycle automation.

Step 4: For activated stores, lead with a value recap and gentle urgency

The “activated” branch is your best segment. They’ve seen something work. Your job is to make sure they see and remember that before the trial flips off.

On this path, UpsellPilot does:

  1. Send Email (3 days left): value recap\n- Subject: “UpsellPilot has added $482 so far. Your trial ends in 3 days.”\n- Body:\n - Personal recap of value: total upsell revenue, top‑performing widget, conversion rate.\n - Simple explanation of what happens at trial end (“widgets pause unless you pick a plan”).\n - Clear CTA to update billing details.

  2. Time Delay: 2 days.

  3. Send Email (1 day left): stronger urgency\n- Subject: “Tomorrow your UpsellPilot widgets pause.”\n- Body:\n - Short restatement of value and cost of pause.\n - A concise pricing table or link to /pricing in your own app.\n - One CTA button to “Choose a plan.”

Configuration in the journey:

  • Two Send Email nodes in sequence on the activated side.

  • Both set to “Send only once”.

  • Time Delay set to 2 days between them (unit: day, value: 2).

If you don’t yet personalize revenue in the email body, you can still make this pattern work with averages or ranges, then improve it later using the email builder and variables. The key is the frame: “Here’s what you’ve gotten so far; here’s what you lose if you stop.”

Skipping this and sending only a generic “Your trial is ending” email costs you conversions from stores that liked you but forgot to upgrade. You’re relying on memory instead of data.

Step 5: For dormant stores, remove friction, not just shout louder

On the “dormant” If/Else branch, the story is different. They have not seen clear value yet. Hammering them with “your trial is ending” is noise, not help.

UpsellPilot uses a softer, diagnostic sequence:

  1. Send Email (3 days left): “Do you want a real test?”\n- Subject: “We haven’t seen UpsellPilot live on your store yet.”\n- Body:\n - Acknowledge that setup may have stalled.\n - List one or two common blockers you hear in support tickets.\n - Offer two paths:\n - 1‑click link to a wizard that gets a default upsell live in under five minutes.\n - Offer of help: link to book a quick setup call, or reply to the email.

  2. Wait Condition: watch for activation\n- Condition: is_activated becomes true (or order_with_upsell triggered at least once).\n- Timeout: 2 days.

  3. If/Else:\n- If they activated during that window, merge them back into the “activated countdown” path.\n- If not, send a final “decide with eyes open” email.

Final dormant‑path email (1 day left):

  • Subject: “Trial ends tomorrow — should we pause or help you finish setup?”

  • Body:\n- Invite a clear no: “If UpsellPilot isn’t a fit, uninstall any time. No hard feelings.”\n- But one more friction‑removal: prefilled “Help me decide” reply, or a link to a 5‑minute checklist.

Journey configuration details:

  • Send Email node specific to dormant users at entry.

  • Wait Condition node with condition on activation event or attribute, 2‑day timeout.

  • If/Else node following Wait Condition, to route activated vs still‑dormant.

  • A Merge node later to keep the canvas sane (more on that next).

This approach respects the user’s context. You’re not begging them to pay for something they barely touched. You’re offering to either get them to meaningful usage or let them walk away cleanly.

Step 6: Converge the paths and handle the actual expiry day

By this point you have three flavors of trialists:

  • Activated early, saw value, and got the Day‑3 + Day‑1 emails.

  • Dormant at Day‑3, then activated during the wait window.

  • Dormant all the way.

All of them hit “trial end date” on the same day. You want one last, clear touch here, not three competing flows.

This is where the Merge process node matters. On the canvas:

  • Connect the “activated” path after its Day‑1 email to a Merge.

  • Connect the dormant‑but‑activated‑late path to the same Merge.

  • Connect the still‑dormant path after its Day‑1 email to a different Merge (so you still keep clean branches).

From each Merge, add:

  • Time Delay: 1 day (to land exactly on trial expiry).

  • One final Send Email for each segment:

For activated:

  • Subject: “Last day of your UpsellPilot trial.”

  • Body:\n- One‑line reminder of added revenue.\n- “Today is the last day your widgets keep running on trial.”\n- CTA to confirm billing or pick a plan.

For still‑dormant:

  • Subject: “Last day to test UpsellPilot risk‑free.”

  • Body:\n- Honest: “We haven’t seen enough data to judge yet.”\n- Clear choice: “Upgrade for a month and give it a real test, or reply and we’ll close your account cleanly.”\n- Optional: for high‑ARPU targets, hint you’re open to extending the trial if they’re willing to do setup now.

Again, both Send Email nodes get “Send only once” toggled on. At this point, more than three emails in three days starts to feel like badgering.

Step 7: Pull yourself in when it can actually change the outcome

Up to now, everything’s been automated. But there are cases where a human touch in the last 48 hours can change the outcome, and you don’t want to babysit dashboards all day.

UpsellPilot adds a simple internal alert:

On the activated branch, after the Day‑1 email and before the final expiry email:

  • Add a Wait Condition:\n- Condition: billing_updated has not triggered.\n- Timeout: 12–24 hours (depending on your appetite).

  • Then add a Send Internal Email node:\n- To: founder or whoever owns customer success.\n- Subject template: “High‑value trial expiring tomorrow without billing on file.”\n- Body: include store name, trial end date, current upsell revenue, and a link to their shop.

You can narrow this with an extra filter (e.g. only if upsell_revenue > $200) so you only get pinged for meaningful accounts.

This balances automation with leverage. You’re not manually chasing every $19/mo trial, but you also aren’t shrugging when a $400/mo Plus store teeters on the edge.

Step 8: Exit cleanly with clear lifecycle tags

Finally, when the journey completes, you want your contact record to tell the truth about what happened. This is critical for win‑backs later.

Use a pair of Wait Conditions and tag updates at the tail:

  1. Wait up to 2 days after expiry to see if they convert:\n- Condition: is_paid becomes true or trial_converted event fires.\n- Timeout: 2 days.

  2. If they convert:\n- Remove Tag: trial-expiring-3d, trial-expiring-1d.\n- Add Tag: customer-active.\n- Optionally Update Contact Attribute: increment trials_started or set converted_at timestamp.

  3. If the timeout hits with no conversion:\n- Remove Tag: trial-expiring-3d, trial-expiring-1d.\n- Add Tag: trial-churned.\n- Optionally Send Internal Email if they’re above a revenue threshold.\n- Then let them exit the journey.

This is where the Brand Message for this pattern comes into focus: most SaaS businesses (and especially Shopify apps) leak lifetime value by not nurturing engagement. Trials stall, expire, and fall out of your mental model. Clear tags and attributes mean future sequences — for re‑activation, feature launches, or upsell — can speak directly to “churned trial” as a state, instead of treating everyone like a cold prospect.

Why this pattern pays for itself for e‑commerce apps

When UpsellPilot wired this “final 3 days” journey in, they didn’t expect miracles. They expected a modest bump. What they got over a quarter was:

  • A 20–30% increase in trial‑to‑paid conversions specifically on Day 13–15.

  • Much higher open and click rates on expiry‑day emails (because they were anchored in value, not vague reminders).

  • A clear list of “trial‑churned” contacts with context for future outreach instead of a pile of anonymous uninstalls.

All without adding a sales team or micromanaging every account.

Underneath those metrics is a simple shift: treat the end of a trial as a lifecycle stage to design for, not just a billing setting. Capture the detail you already have on each store, speak to activated and dormant trials differently, and let automation do the repetitive work while you and your team step in only where it counts.

Spreeflo’s journeys, segment builder, and triggers make that wiring straightforward: one Criteria Match trigger, a couple of If/Else splits, some Time Delays and Wait Conditions, a few Send Email nodes, and a well‑placed Send Internal Email are usually enough.

The real work is deciding what “activated” means in your app and writing honest copy that reminds people of the value they’re already getting — or helps them see it before the clock runs out.

Do that, and the quiet churn at the end of your trials starts looking a lot more like quiet, compounding MRR.