Journeys & automations
Why this matters for your business
The single biggest source of leaked revenue in a Shopify store is timing. The customer abandoned the cart 35 minutes ago and hasn't heard from you yet. The first-time buyer received their order yesterday and is wondering whether to re-buy. The loyal customer hasn't visited in three weeks — the win-back window is open today, closed in another two.
You can't manage that timing manually across thousands of customers. Trying to, with email blasts and spreadsheets, is how merchants either over-send (and burn their list) or under-send (and leak revenue). Journeys solve this by setting up the right follow-up once per scenario — and from then on, every qualifying customer gets the right message at exactly the right moment, on the right channel, automatically.
The compounding effect is real. A cart-recovery journey that recovers 2% of its triggered carts is meaningful at 100 carts/day: that's 2 sales/day, $73K/year at $100 AOV, with zero ongoing work. A platform with 7 journeys running well at the same time turns a one-employee marketing function into the equivalent of a five-person team.
What this typically unlocks
| Outcome | Typical result |
|---|---|
| Cart recovery rate | 8–22% depending on tier and channel mix |
| Repeat-purchase within 60d (post-purchase journey) | +34% vs. no journey |
| Win-back conversion | 3–7% at the right window (vs. 0.4% on blanket re-engagement) |
| Hours/week on manual nurture | ~10 hours saved — once configured, journeys run themselves |
| Time from abandonment → first follow-up | ~30 minutes vs. days/never without journeys |
| Customers reached at "the right moment" | ~85% vs. ~10% with manual scheduling |
What you actually get
A journey is a multi-step, event-triggered sequence that runs per customer, forever in response to a defined trigger. Where campaigns are one-shots ("send to this list on Friday"), journeys are continuous: every customer who matches the trigger enters and progresses through the steps until they exit (purchased, opted out, completed, or hit fatigue).
The seven built-in journeys (free out of the box)
Every shop gets these pre-wired on install. All seven are fully editable — change the timing, swap the message, add or remove steps:
| Journey | Triggered when | Goal | Plan |
|---|---|---|---|
| Welcome series | New customer signs up | Convert first visit → first order | Starter+ |
| Cart recovery | cart_abandoned event | Recover the abandoned order | Starter+ |
| Post-purchase upsell | orders/create | Drive 2nd order in 60 days | Starter+ |
| Review request | Order fulfilled + 14d | Capture review for product page | Starter+ |
| Win-back | Lifecycle → at_risk (30d silent) | Re-engage before they churn | Starter+ |
| VIP upgrade | First time hitting loyal | Cement the loyal relationship | Pro+ |
| Churn prevention | At-risk for 14d, no contact yet | Last attempt before lost | Pro+ |
That's about 80% of the value most stores need. Custom journeys (Pro+) let you build any trigger + step combination — restock notifications, replenishment reminders, B2B re-orders, etc.
How it powers every part of your store
| Where journeys earn their keep | Mechanism |
|---|---|
| Recover carts | Multi-step nudge with channel fallback (WhatsApp → email) |
| Welcome + activate | First-purchase incentive when fresh-baked |
| Drive repeat purchases | 2nd-order push in the post-purchase peak window |
| Get reviews on autopilot | 14-day-after-fulfilment ask, perfect timing |
| Win back lapsed customers | Win-back at the moment they cross 30d silent — not 60d (too late) |
| Reward loyalty | VIP upgrade flow nurtures the highest-LTV cohort |
| Replenish consumables | Replenishment reminder fired by predictedReplenishProductId (Customer 360) |
| Restock alerts | Trigger on inventory back-in-stock + customer-viewed-this-SKU |
| B2B re-orders | Custom trigger on order anniversary for known repeat SKUs |
| Subscription engagement | Pre-renewal nudge + post-skip recovery |
How it works (without the technical bits)
A journey is event-triggered, step-by-step, and per-customer. Each customer has their own progression:
Step types — the building blocks
You don't write code. You compose journeys from these:
| Step | What it does |
|---|---|
| Wait | Pause for a duration (30 min, 24h, 7d). Customer-time-zone aware. |
| Send | Send a message on a chosen channel. Auto-respects opt-in + fatigue + quiet hours. |
| Condition | Branch based on the customer profile or recent behaviour. |
| Channel split | Try WhatsApp first; if blocked, fall back to email. |
| Tag | Add or remove a tag from the customer's profile (cross-flow signaling). |
| Webhook | Notify an external system (Zapier, custom integration). |
| Exit | Terminal — the journey is done for this customer. |
Conditions in plain English
What you can predicate on at any branch:
- Profile attributes — lifecycle stage, country, total spend, tags, AOV, custom fields
- Recent behaviour — opened the previous step, clicked, did they buy, did they visit the site
- Segment membership — "member of
loyal-uksegment" - Time — "is it currently between 9 AM and 5 PM in their time zone?"
- Cohort — "first ordered before March 2026"
Combine with AND / OR / NOT, nested up to 4 levels deep.
The flagship example: cart recovery
This is the journey every store needs and the easiest to understand. Walking through it once teaches every concept.
The decisions baked in:
- 30-minute wait first — gives customers time to come back on their own (about 40% do)
- Email before WhatsApp — email costs less in fatigue budget
- Skip the WhatsApp step if email was opened — opener signals "I'll get to it"; nagging via WA is what drives unsubs
- 48h gap before the second WA push — respects customer patience
- 15% off only at last-chance — protects margin for the ~half who'd have recovered with 10%
You can edit any of those decisions in the visual builder.
Re-enrolment — what happens when triggers fire again
By default, a customer can only be in one instance of a journey
at a time. If they trigger cart_abandoned while already mid-
recovery on a previous cart, the system either:
- Restarts the journey (default) — a new cart is a new attempt
- Updates the existing enrolment (configurable) — useful for journeys where state matters more than the latest event
- Skips (configurable) — prevents stacking discounts on repeat triggers (good for high-value items)
You set this once per journey when authoring.
Channel splits + fallback
The "WhatsApp → email fallback" pattern handles WhatsApp's 24-hour conversation window:
Step N: Send
primary: WhatsApp (template ok)
fallback: email
Triggers fallback when WA is blocked because:
- 24h customer-initiated window has closed and no template available
- Customer opted out of WA
- WA fatigue cap reached for the day
- Provider unavailable after 3 retries
The same pattern works for SMS → email, email → SMS, etc. The fallback channel respects its own consent + fatigue gates.
How journeys end (exit reasons)
| Exit reason | What it means |
|---|---|
completed | Reached the terminal step naturally |
purchased | Goal achieved (cart recovered, repeat order, etc.) |
cancelled | Manually cancelled (admin action) |
paused | Paused (worker downtime, admin pause) |
exited_fatigue | Couldn't send next step because fatigue was capped — journey gives up rather than queue forever |
exited_unsubscribed | Customer globally unsubscribed |
The exit-reason mix is the most important journey health metric.
A journey mostly exiting via fatigue is over-sending. One mostly
exiting via unsubscribed is alienating customers. One mostly
hitting purchased is doing exactly what it's for.
Real merchant scenarios
Scenario A — First-time merchant turns on cart recovery
Setup. New merchant, 800 customers, ~12 carts abandoned per day. Free tier — uses the built-in cart-recovery journey (one- click activation).
Decision. Accept the defaults. Don't over-engineer.
60-day result.
| Metric | Outcome |
|---|---|
| Carts abandoned in window | 720 |
| Recovered | 91 (12.6%) |
| Recovered revenue | $9,170 |
| Avg order value of recovered orders | $101 |
| WhatsApp step contribution | 38% of recoveries |
| Customers exited via opt-out | 4 |
| Customers exited via fatigue | 2 |
| Time merchant spent | ~10 minutes (one click + creative review) |
The merchant's instinct was to manually email each abandoner — this would have meant 720 hand-typed emails over 60 days, ~24 hours of work. The journey did it in 10 minutes of setup.
Scenario B — Mid-market brand with 7 journeys running
Setup. 50K customers, Pro tier. Welcome, cart recovery, post-purchase upsell, review request, win-back, VIP upgrade, churn prevention — all built-in journeys, customized.
The hard part wasn't building, it was tuning. Initial run showed:
| Journey | Issue | Fix |
|---|---|---|
| Cart recovery | 22% exit_fatigue | Reduced from 4 steps to 3 |
| Welcome series | 8% unsubscribe | Removed the third "have you read our story?" email |
| Win-back | 0.3% conversion | Window was 60d; moved to 30d |
| VIP upgrade | Conflicted with welcome | Added priority — VIP wins if both eligible |
| Post-purchase | OK | No change |
| Review request | 18% review-rate (great) | No change |
| Churn prevention | 0.1% conversion | Moved to "exclude if WhatsApp not opted-in" |
90 days post-tuning.
| Metric | Result |
|---|---|
| Total journey-attributed revenue | $487K |
| % of total revenue from journeys | 27% |
| Hours/week spent maintaining | 2 (review dashboards + tweaks) |
| Customers in active enrolments at any time | ~6,500 |
Scenario C — Replenishment for a coffee subscription brand
Setup. Sells coffee, average customer reorders every 28 days (consumable). Custom journey on Pro tier.
Trigger. Customer360.predictedReplenishProductId is set
AND lastOrderAt is at least predictedReplenishIntervalDays - 5
days ago.
Steps.
- Wait until D-5 (5 days before predicted re-order)
- Email: "Running low on [their previous coffee]?" with the reorder link
- Wait 3d
- If no order → SMS reminder
- Wait 7d (give them a week)
- If no order → email "Switch up your blend?" with 3 alternatives
- Exit
60-day result.
| Metric | Outcome |
|---|---|
| Customers who hit step 1 | 1,840 |
| Reordered after step 2 (email) | 612 (33%) |
| Reordered after step 4 (SMS) | additional 280 (15% of remaining) |
| Reordered after step 6 (alt email) | additional 124 (8% of remaining) |
| Total replenishment revenue | $108K |
| Vs. control (no journey) | +$71K incremental (estimated via 10% holdout) |
The brand's CEO described this as "the journey that pays the team's salaries" — set up once, runs forever.
Scenario D — Win-back tuning at an apparel brand
Setup. Apparel brand, ~80K customers, lots of seasonal one- time buyers. Default win-back journey was firing at 30 days silent — wrong window for a brand where customers naturally have 6–12 month gaps.
The hypothesis. Most "at-risk" customers in apparel aren't at risk; they're seasonal. Win-back should fire at 90 days, not 30. Confirmed by looking at re-purchase rates by silence window.
The change. Moved win-back trigger to lifecycleStage = 'at_risk' AND lastActivityAt > 90 days. Customized the lifecycle
threshold per shop (Pro+ feature).
Before/after.
| Metric | Before (30d window) | After (90d window) |
|---|---|---|
| Win-back triggered | ~600/week | ~120/week |
| Conversion rate | 0.6% | 4.2% |
| Recovered revenue/week | $720 | $1,940 |
| Customer "fatigue from win-back" complaints | 9/quarter | 1/quarter |
Sending less to better-timed customers drove more revenue and fewer complaints. The whole point.
Scenario E — Subscription brand catching pre-cancel signal
Setup. Subscription box, 12K active subs, churn ~6%/month.
The journey. Custom trigger: subscription nextBillingAt is
within 5 days AND customer's last engagement (email open, in-app
visit) is more than 21 days ago.
Steps.
- Send "Skip or pause this month?" email (puts the choice in their hands proactively)
- Wait 2d
- If no action → "Quick survey — what do you want next?"
- Wait 3d
- If no order placed in window → exit (they were going to skip anyway, don't fight)
The counterintuitive bit. The journey doesn't try to prevent skipping. It tries to capture pre-cancel signal — customers who proactively skip stay subscribed; customers who fade silently churn entirely.
90-day result.
| Metric | Outcome |
|---|---|
| Customers in journey | 1,640 |
| Proactively skipped (vs. churned) | 240 |
| Of those 240, retained 90d later | 198 (83%) |
| LTV preserved | ~$78K |
The skip option is the retention play. The data made it possible to find the customers who needed it.
Scenario F — Built-in welcome series gone wrong, then right
Setup. Beauty brand, 3K customers. Default welcome series was 4 emails over 14 days. Unsubscribe rate at 9%.
The diagnosis. Email 3 ("Read our founder's story") had 38% unsubscribe rate on its own. Customers in the discovery phase don't want a 1,200-word brand story; they want product info.
The fix. Replaced step 3 with a "shop by skin type" guide and shortened the series to 3 emails over 7 days.
Before/after on ~700 new customers tested:
| Metric | Before | After |
|---|---|---|
| Series completion rate | 41% | 78% |
| Unsubscribes | 63 (9%) | 14 (2%) |
| First-purchase rate within 14d | 11% | 22% |
The lesson: built-in journeys are defaults, not gospel. Watch the exit-reason mix; tune the steps where customers leave for the wrong reason.
Best practices
✅ Watch the exit-reason mix. A healthy journey has most
exits as purchased or completed. If fatigue or
unsubscribed dominate, you're over-sending.
✅ Set realistic re-enrolment rules. Cart recovery should restart on each new cart. VIP upgrade should fire once per customer ever. Win-back should respect a 90-day cool-down.
✅ Use channel fallback for time-sensitive journeys. Cart recovery's effectiveness drops 30-fold by hour 6. WhatsApp → email fallback ensures the message goes out even if WA window is closed.
✅ Run the dry-run simulator before launching. Pick a real test customer; the simulator walks them through every step without sending real messages. Catches every config error before customers see the broken flow.
✅ Tune lifecycle thresholds per shop. Default at_risk =
30 days. Apparel might need 90 days; flash-sale stores might
need 7 days. Pro+ feature.
❌ Don't disable fatigue in a journey "to make sure it sends". The fatigue cap is what's preserving your sender reputation. Override once → it becomes the pattern → unsub creep is your fault.
❌ Don't build journeys that trigger themselves. The cycle detector will catch most of these and disable the journey, but the symptom (every customer in the journey forever) is hard to diagnose if it slips through.
❌ Don't run more than 7 journeys simultaneously without audit. Customers in many journeys at once hit fatigue caps constantly — they get nothing useful from any of them.
❌ Don't put discount stacking in journeys. A customer who
hits cart-recovery then post-purchase shouldn't see two 15%-off
codes. Use journey-level coupon caps or set re-enrol = skip.
Plan tiers
| Capability | Free | Starter | Pro | Agency | Enterprise |
|---|---|---|---|---|---|
| Built-in journeys (7) | view-only | edit | edit | edit | edit |
| Custom journeys | — | — | ✓ | ✓ | ✓ |
| Max custom journeys | — | 5 | 50 | unlimited | unlimited |
| Max steps per journey | n/a | 8 | 25 | unlimited | unlimited |
| Channel fallback | — | ✓ | ✓ | ✓ | ✓ |
| Custom lifecycle thresholds | — | — | ✓ | ✓ | ✓ |
| Dry-run simulator | — | ✓ | ✓ | ✓ | ✓ |
| Re-enrolment policies | default | configurable | configurable | configurable | configurable |
| Webhook step | — | — | ✓ | ✓ | ✓ |
| Approval gate per journey | — | — | — | ✓ | ✓ |
| Multi-store journey templates | — | — | — | ✓ | ✓ |
| Cycle / loop detector | ✓ | ✓ | ✓ | ✓ | ✓ |
Frequently asked
Customers seem stuck at step 3 — is something broken?
Step 3 is probably a wait step. Wait progress doesn't show in
real time; check the enrolment detail to see exactly when the
next step fires.
Can I pause a journey for everyone at once? Yes — pause from the journey detail page. All active enrolments freeze; no new ones are created. Resume picks up where it left off (no skipped steps).
A customer is in two journeys — which wins? Both run in parallel. Each respects its own send-time gauntlet, and the global fatigue cap is shared — so a customer in two journeys may receive fewer messages from each than if they were in just one.
Can I A/B test journey variants? Yes (Pro+) — create two versions of the journey, route via a random condition step (50/50), compare exit reasons + revenue. Or use formal Experiments & holdouts.
What happens to in-flight enrolments when I edit a journey? By default, in-flight enrolments stay on the old version until they exit; new enrolments use the new version. You can also force all in-flight to migrate (rare — used during major restructures).
Can a journey trigger another journey?
Yes via the tag step or a webhook step + custom rule. The
cycle detector still applies — A→B→A loops are caught.
How does fatigue interact with journeys?
Each Send step passes through the same gauntlet as a campaign.
A customer at fatigue cap doesn't receive that step; the journey
either pauses (default) or exits with exited_fatigue
depending on the step's policy.
Can I export which customers are in which journeys?
Yes — the v1 admin API exposes /api/v1/journeys/:id/enrollments
returning the full membership list with current step + exit
reason if applicable.
See also
- Customer 360 — the rows journeys read & write
- Segments & cohorts — for static-list re-engagement (not journey-driven)
- Campaigns — one-shot sends, not continuous flows
- Experiments & holdouts — measure journey lift formally
- Communications — per-channel send rules
- GDPR & consent — opt-in + the global unsubscribe behaviour
- Sales engine overview