> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.onesignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile-first lifecycle Journeys

> Welcome, trial-to-paid, event-driven, retention, and win-back Journey playbooks for mobile-first apps, with both basic and advanced data integrations covered.

This guide implements the [Mobile-first strategy](./mobile-first) as five lifecycle Journeys: welcome, trial-to-paid conversion, event-driven, retention, and win-back. Each Journey supports two implementation paths so you can ship value before all your Custom Event tracking is in place:

* **Case 1 – Basic:** Use only the data the OneSignal SDK collects automatically — `First session`, `Last session`, and built-in Subscription attributes. No Custom Events or Tags required.
* **Case 2 – Advanced:** Use [Custom Events](./custom-events) and [Tags](./add-user-data-tags) for behavior-triggered, per-user personalization.

Start with Case 1 and layer in Case 2 components as your data integrations mature.

<Note>
  This page covers the five core mobile-first Journey shapes. More examples can be found in [Tutorials](./tutorials).
</Note>

## Prerequisites

Work through the [Mobile-first strategy implementation roadmap](./mobile-first#implementation-roadmap) before building Journeys here. The strategy page defines the data foundation; this page implements it.

**Required:**

* **External IDs** assigned at signup, login, and session resume — see [External ID setup](./users#external-id).
* Access to [Journeys](./journeys-overview) in your OneSignal dashboard.
* At least one configured channel: push (with permission prompted), email, or SMS.

**Recommended:**

* An [in-app message that prompts for push permission](./prompt-for-push-permissions) — required for the Case 1 welcome flow's first step.
* **Case 2 only:**
  * **Core Tags**: `account_type`, `core_feature_used`, `trial_end_date` — see [Tags](./mobile-first#tags).
  * **Core Custom Events**: `signup_completed`, `core_feature_used`, `trial_converted`, `subscription_cancelled` — see [Custom Events](./mobile-first#custom-events).
  * **Starter segments**: Active Trial, Trial Expiring, Core feature untouched, At-Risk Subscribers, Lapsed — see [Segments](./mobile-first#segments).

If you don't have Custom Events yet, build the Case 1 versions of welcome, retention, and win-back while your event tracking comes online. Trial-to-paid conversion requires a `trial_end_date` Tag in both cases.

## Welcome Journeys

The welcome Journey's job is to set new users up for success: drive them to their Aha moment before they churn. The first 7 days determine whether a new user becomes a long-term customer or a churn statistic, so the welcome Journey runs across the full week with gentle daily nudges. Four principles drive the structure:

1. **Capture permissions early.** Drive push opt-in via a Day 1 in-app message during or right after onboarding. Without push permission, every later welcome step that uses push goes unread.
2. **Introduce the product.** Pair the IAM with a Day 1 email for consenting users that introduces what the product does, what to do next, and what messages to expect from you.
3. **Nudge toward the core feature daily.** Days 2–6 each carry a single, gentle reminder tied to a feature, use case, or setup step — never a streak claim or a hollow "we miss you."
4. **End the week deliberately.** A Day 7 closing email recaps the user's first week and transitions them from "trying the product" to "part of the community."

<Note>
  The "Aha moment" is the single core action most predictive of conversion or retention in your app. Identify it before you build the Journey. For a workout app it might be logging the first session; for a finance app, linking the first account.
</Note>

### Welcome Journey configuration

Both cases share segments, templates, and Journey settings. Build them once and reference from either case.

#### Welcome Journey segments

**Entry:**

* **Case 1**: Built-in segment `First Session < 1 day ago` AND `Last Session > 4 hours ago`. The user matches the moment they cross 4 hours of inactivity inside their first day — exactly when the Day 1 message sequence should fire, not the moment of signup.
* **Case 2**: Custom Event entry on `signup_completed` (or `trial_started` for trial-based apps), then a 4-hour Wait so the first message lands after the user finishes their initial session.

**Recent activity check (Case 1 only):**

* **Name:** "Active in the last 12 hours"
* **Filter:** `Last Session < 12 hours ago`

The Day 2–7 Yes/No branches use this segment to skip the daily push if the user has already been active. Twelve hours is a deliberate compromise — long enough that the user feels like they "just used" the app, short enough that yesterday's evening session doesn't suppress today's nudge.

**Exit conditions:**

* **Case 1**: Time-based — Journey completes after the Day 7 closing email.
* **Case 2**: Event-based — exit on `core_feature_used = "1"` or `onboarding_completed`. Users who activate stop receiving feature-nudge messages.

#### Welcome Journey templates

These templates are starting points — clone, edit, and re-use them in your own Journey. They use [Liquid syntax](./using-liquid-syntax) for personalization.

<Tabs>
  <Tab title="Push templates">
    | Day | Name                 | Purpose                                            |
    | :-: | -------------------- | -------------------------------------------------- |
    |  1  | Day 1 — First action | Deep link to the highest-leverage first action     |
    |  2  | Day 2 — Quick win    | 60-second action that reinforces value             |
    |  3  | Day 3 — Feature tip  | A useful capability most users miss in week one    |
    |  4  | Day 4 — Use case     | Connect the product to a realistic outcome         |
    |  5  | Day 5 — Setup nudge  | Incomplete-profile or preferences reminder         |
    |  6  | Day 6 — Community    | Soft invite to community, social, or share moments |
    |  7  | Day 7 — Week 1 wrap  | Final value reminder before the closing email      |

    Keep copy short and benefit-led. Deep-link every push to the exact screen where the user can take the action — see [Deep linking](./deep-linking).

    <Columns cols={2}>
      <Card title="Templates" icon="clone" href="./templates">
        Create and manage reusable message templates.
      </Card>

      <Card title="Message personalization" icon="wand-magic-sparkles" href="./message-personalization">
        Pull Tag values into push, email, and IAM copy with Liquid.
      </Card>
    </Columns>
  </Tab>

  <Tab title="Email templates">
    Two emails fire — one on Day 1, one on Day 7 — each playing a distinct role. See [Email messaging](./email-messaging) for setup.

    **Day 1 — Welcome and push opt-in**

    *Purpose:* Open the Journey with a value-led welcome that also drives push opt-in for users who declined the install-time prompt.

    *Content outline:*

    * A short welcome and product framing — what the user can do, not a feature tour.
    * 1–2 next-step CTAs deep-linking into the app (e.g., complete profile, try the core feature).
    * A **turn-on-notifications** CTA that deep-links to your in-app preference center or notification settings screen. This catches users who declined install-time push without forcing a fresh OS prompt — see [Prompt for push permission with in-app messages](./prompt-for-push-permissions) for the in-app prompt pattern that pairs with this CTA.
    * Expectation-setting on what messages they'll receive and how often.
    * Help center or support links so users don't have to email you with questions.

    **Day 7 — First week recap and what's next**

    The closing email at the end of the Journey. Where Day 1 welcomes the user into the product, this email transitions them from "trying the product" to "part of the community."

    *Purpose:* Reinforce progress, surface what's still unexplored, and give the user a reason to look forward to Week 2.

    *Content outline:*

    * A first-week recap — actions completed, key features used. Personalize via Tags or [Liquid syntax](./using-liquid-syntax) where you have the data.
    * A **next chapter** section: 2–3 things to try in Week 2 (advanced features, integrations, settings).
    * A community or social CTA — Discord, X, blog, or your customer community.
    * A teaser for what's coming next (upcoming feature, content drop, or a Day 14 milestone).

    <Columns cols={2}>
      <Card title="Templates" icon="clone" href="./templates">
        Create and manage reusable message templates.
      </Card>

      <Card title="Email warm-up" icon="envelope" href="./email-warm-up">
        Required if you're sending Welcome email at scale on a new domain.
      </Card>
    </Columns>
  </Tab>

  <Tab title="In-app message templates">
    In-app messages fire when the user is *already in the app* — the highest-leverage moment to drive setup completion, push opt-in, and feature engagement. Three IAMs cover the Journey's main inflection points.

    **Day 1 — Onboarding flow**

    Use a multi-card or carousel IAM to funnel new users into the two highest-leverage setup steps:

    1. Profile / preferences completion
    2. Push opt-in via a **Push Permission Prompt** click action

    Users who finish both are dramatically more likely to stick. Profile investment creates sunk-cost attachment, and without push opt-in, the rest of this Journey can't reach them outside the app.

    <Info>
      A **Push Permission Prompt** IAM will not display if the user has already granted push permission, so it's safe to leave the prompt in even for users who opted in elsewhere.
    </Info>

    **Day 3 — Feature spotlight**

    A single-card IAM that highlights one secondary feature most users miss in their first week. Pick a feature with high adoption-to-retention correlation (e.g., the second tier of the core workflow, an integration, or a personalization setting).

    1. A specific benefit ("Save 10 minutes a week with \[feature]")
    2. A primary CTA that deep-links to the feature
    3. A dismiss action that doesn't re-show on the same session

    **Day 6 — Community and share**

    A soft-invite IAM that introduces the user to your broader community. Pick whichever touchpoint is most natural for your product:

    * Join a community channel (Discord, Slack, Reddit, customer forum).
    * Share a milestone or referral code with friends.
    * Follow your social accounts for tips and updates.

    Keep the ask low-stakes — this is the inflection from "user" to "community member," not a hard upsell.

    <Columns cols={2}>
      <Card title="Design your in-app message" icon="pen-ruler" href="./design-your-in-app-message">
        Full reference for the IAM editor, blocks, and Carousel.
      </Card>

      <Card title="Prompt for push permission" icon="bell" href="./prompt-for-push-permissions">
        Configure a **Push Permission Prompt** click action on a Day 1 IAM.
      </Card>
    </Columns>
  </Tab>
</Tabs>

#### Welcome Journey settings

Entry and exit rules are covered in [Welcome Journey segments](#welcome-journey-segments) above. In addition, configure these Journey-level settings:

* **Future additions only**: **Checked** — so existing users aren't bulk-enrolled the moment the Journey is saved.
* **Re-entry rules**: **Disabled** — each user receives this Journey only once.

#### Daily timing: Wait + Time Window (Case 1)

Case 1 uses two Journey actions together to deliver a daily nudge during the user's most likely engagement window:

1. **[Wait](./journeys-actions#wait):** A fixed delay between days.
2. **[Time Window](./journeys-actions#time-window):** A delivery window in the user's local timezone (default: 6 PM – 10 PM). Each day's push is held until the window opens.

Set the Wait to the **Time Window duration + a 2-hour buffer**. For a 4-hour evening window, the Wait is **6 hours** — the buffer guarantees the Wait always expires after the window closes, so the next push is held for the *following* day rather than re-firing the same evening. See [Welcome Journey: Mobile gaming](./welcome-journey-mobile-gaming#daily-timing-wait--time-window) for the full timing math.

<Tip>
  Adjust the Time Window to your audience. B2B productivity apps may prefer a 9 AM – 12 PM weekday window; consumer wellness apps may prefer 7 AM – 9 AM. Evening is a strong default for general consumer apps but worth A/B testing against your usage data.
</Tip>

<Tabs>
  <Tab title="Case 1 – Basic">
    The Basic flow uses time-based delays and the recent-activity Yes/No branch — a "set it and forget it" 7-day welcome that requires no Custom Events or Tags.

    ##### Day 1

    <Steps>
      <Step title="Email: Day 1 — Welcome and push opt-in">
        **Goal:** Open the Journey with an appreciative, value-led email that also drives push opt-in.

        Because this is the first message step, the email lands 4 hours after the user's first session ends (per the entry segment) — less needy, more deliberate. The email's push opt-in CTA also catches users who declined the install-time permission prompt, deep-linking them to the in-app preference center.
      </Step>

      <Step title="In-App Message: Day 1 — Onboarding flow">
        **Goal:** Drive profile completion and push opt-in the next time the user opens the app.

        Queue the Day 1 onboarding IAM so it displays on the user's next session. The flow funnels them into:

        1. Profile / preferences completion
        2. Push opt-in via a **Push Permission Prompt** click action

        <Warning>
          Make sure this IAM doesn't overlap with other [push permission prompt strategies](./prompt-for-push-permissions) outside the Journey. Two prompts firing back-to-back leads to one being silently skipped — better to remove it from this IAM than have both trigger.
        </Warning>
      </Step>

      <Step title="Time Window: 6 PM – 10 PM">
        **Goal:** Hold the Day 1 push until the evening engagement window.

        The entry segment can trigger overnight or early morning. Without the Time Window, an inactivity threshold crossed at 2 AM would push at 6 AM. This holds the push until 6 PM in the user's timezone.
      </Step>

      <Step title="Push Notification: Day 1 — First action">
        **Goal:** Drive a second session by deep-linking to the highest-leverage first action.

        Keep copy short and benefit-driven — "Here's the one thing to try first" works as a starting point. Deep-link to the exact screen where the user can take that action, not the app home.
      </Step>
    </Steps>

    ##### Day 2

    <Steps>
      <Step title="Wait: 6 hours">
        **Goal:** Push the next Time Window evaluation past 10 PM so the window snaps to the next day rather than re-firing the same evening. See [Daily timing](#daily-timing-wait--time-window-case-1) for the math.
      </Step>

      <Step title="Time Window: 6 PM – 10 PM">
        **Goal:** Hold the next push until the evening engagement window.
      </Step>

      <Step title="Yes/No branch: Active in the last 12 hours">
        **Goal:** Skip the push if the user has already returned to the app.

        **Setup:** Segment membership: "Active in the last 12 hours."

        This creates two branches:

        * **Yes:** User was active in the last 12 hours → skip the push and continue to the next day's Wait.
        * **No:** User has not returned → send the Day 2 push on the No branch.
      </Step>

      <Step title="Push Notification: Day 2 — Quick win">
        **Goal:** Bring the user back with a 60-second action that reinforces value.

        Add this to the **No** branch of the Yes/No step.
      </Step>
    </Steps>

    ##### Days 3–6

    Each day repeats the Day 2 pattern — **Wait 6 hours → Time Window 6 PM – 10 PM → Yes/No branch → push on the No branch**. Drop in the per-day push template (`Day 3 — Feature tip`, `Day 4 — Use case`, `Day 5 — Setup nudge`, `Day 6 — Community`).

    Insert the **Day 3 feature spotlight** and **Day 6 community/share** IAM steps *before* their corresponding day's 6-hour Wait, so the IAM is queued and ready to display the next time the user opens the app.

    ##### Day 7

    Day 7 follows the same four-step pattern as Days 2–6, then adds the closing email after the branches converge:

    <Steps>
      <Step title="Wait: 6 hours">
        **Goal:** Push the next Time Window evaluation past 10 PM so the window snaps to the next day.
      </Step>

      <Step title="Time Window: 6 PM – 10 PM">
        **Goal:** Hold the Day 7 push until the evening engagement window.
      </Step>

      <Step title="Yes/No branch: Active in the last 12 hours">
        **Goal:** Skip the push if the user has already returned to the app.

        **Setup:** Segment membership: "Active in the last 12 hours."

        * **Yes:** User was active in the last 12 hours → skip the push and continue to the closing email.
        * **No:** User has not returned → send the Day 7 push on the No branch, then continue to the closing email.
      </Step>

      <Step title="Push Notification: Day 7 — Week 1 wrap (No branch)">
        **Goal:** Send a final value reminder before the closing email lands.

        Add this to the **No** branch of the Yes/No step.
      </Step>

      <Step title="Email: Day 7 — First week recap and what's next">
        **Goal:** Transition the user from "trying the product" to "part of the community."

        Add this email step *after* the Yes/No branch's two paths converge — not on either branch. Placing it after convergence means active and inactive users both receive the milestone email, regardless of whether they took the Day 7 push.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Case 2 – Advanced">
    Case 2 is structurally identical to Case 1 — same Day 1 setup, same Day 3 and Day 6 IAMs, same Day 7 closing email. The only change is from Day 2 onward: replace Case 1's **Wait + Yes/No branch** with a single [Wait Until](./journeys-actions#wait-until) node that listens for `core_feature_used` (or another high-signal Custom Event). The Time Window stays.

    This swaps a time-based heuristic ("did the user open the app in the last 12 hours?") for a behavioral fact ("did the user actually do the thing the Journey is nudging?"). Users who hit the event take the event branch and continue silently — no push lands on top of an active session, and active users still receive the closing Day 7 email.

    ##### Required data

    | Type         | Name                 | Description                                                                                                                                                                        |
    | ------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | Custom Event | `core_feature_used`  | Fires when the user takes the Aha-moment action. Dedupe to **once per calendar day in the user's timezone** so same-day repeats don't advance the Journey through subsequent days. |
    | Tag (string) | `first_name`         | *Recommended.* Used by the email and push templates for `{{ first_name \| default: 'there' }}` personalization.                                                                    |
    | Tag (string) | `acquisition_source` | *Optional.* Lets you vary Day 1 push and email copy by acquisition channel.                                                                                                        |

    ##### Day 1

    Identical to Case 1: Email → IAM → Time Window → Push. The only difference is the entry rule — Case 2 enters on the `signup_completed` Custom Event with a 4-hour Wait, instead of the inactivity-based segment. Switch to the **Case 1 – Basic** tab above to see the full Day 1 step list.

    ##### Days 2–6

    Days 2 through 6 share a single pattern. Each day uses a Wait Until node in place of Case 1's Wait + Yes/No combo. The Time Window and the day's push move onto the expiration branch; the event branch stays silent.

    <Steps>
      <Step title="Wait Until: core_feature_used">
        **Goal:** Branch on whether the user has hit their Aha moment in the last day.

        **Condition:** `core_feature_used` Custom Event fires.
        **Expiration:** **22 hours**.

        * **Event branch** — user activated on their own. Continue silently; no push, no email.
        * **Expiration branch** — no activation in 22 hours. Continue to the Time Window and the day's push.

        <Tip>
          **Scope each Wait Until to a specific day with an event property.** Fire `core_feature_used` with a property like `day` that increments per session day, and have each day's Wait Until match the corresponding value. This prevents a late-arriving event from an earlier day from satisfying a later day's Wait Until and gives you cleaner per-day funnel analytics. See [Event Matching](./journeys-actions#event-matching).
        </Tip>
      </Step>

      <Step title="Time Window: 6 PM – 10 PM (expiration branch only)">
        **Goal:** Hold the day's push until the evening engagement window.

        On the expiration branch only. Same Time Window pattern as Case 1, kept here so the push always lands in the evening regardless of when the Wait Until expired.
      </Step>

      <Step title="Push Notification: day's template (expiration branch only)">
        **Goal:** Send the day's nudge — `Day 2 — Quick win`, `Day 3 — Feature tip`, etc.

        After this step, both branches converge and continue to the next day's Wait Until.
      </Step>
    </Steps>

    **IAM placement.** Insert the **Day 3 feature spotlight** and **Day 6 community/share** IAM steps *before* their corresponding day's Wait Until node, so the IAM is queued and ready to display the next time the user opens the app.

    ##### Day 7

    Day 7 follows the same Wait Until pattern as Days 2–6 and adds the closing email after the branches converge:

    <Steps>
      <Step title="Wait Until + Time Window + Push (expiration branch)">
        Same shape as Days 2–6: a Wait Until on `core_feature_used` with a 22-hour expiration, then the Time Window and `Day 7 — Week 1 wrap` push on the expiration branch. The event branch stays silent.
      </Step>

      <Step title="Email: Day 7 — First week recap and what's next">
        **Goal:** Transition the user from "trying the product" to "part of the community."

        Add this email step *after* both branches converge — not on either branch. Active and inactive users both receive the milestone email, regardless of whether the push fired. Personalize the recap section using Tags you've collected during the week (`core_feature_used`, completed-setup signals, etc.).
      </Step>
    </Steps>

    ##### Optional: route activated users to a separate post-activation Journey

    If you want to do more than fall silent on activation, configure the Journey **exit rule** to fire on `core_feature_used = "1"` and use that Tag (or a `welcome_completed = "true"` Tag set in a Tag User step) as the entry filter for a follow-up Journey focused on adoption depth — second-tier features, integrations, or expansion offers.
  </Tab>
</Tabs>

<Tip>
  **Track time-to-Aha**, not just open rate. The single best indicator of welcome Journey health is how quickly new users hit your core feature for the first time. Compare your control group to users who completed the welcome Journey to measure lift.
</Tip>

## Trial-to-paid conversion

The trial-to-paid Journey converts trial users to paying subscribers in the days before their trial ends. Where the Welcome Journey teaches the product, this Journey closes the deal — value reminders, time pressure, and a clear upgrade ask calibrated to where the user is in the trial. Three principles drive the structure:

1. **Time pressure beats discount.** A trial-ending reminder converts higher than a blanket "20% off" email. Use `trial_end_date` for a real countdown; reserve discounts for the final day or the win-back Journey.
2. **Activation status changes the message.** A trial user who hit the core feature gets "Keep going — your data and progress carry over"; one who didn't gets "Try `{{ core_feature_name }}` before your trial ends — it's the part most subscribers love." Same Journey, branched copy.
3. **Exit on conversion, immediately.** Add an exit on `trial_converted` (or `account_type = "paid"`) so converted users stop receiving "trial ending" pushes. This is the single most damaging conversion-Journey mistake — see the [mobile-first FAQ](./mobile-first#faq) for the suppression pattern.

### Trial-to-paid configuration

**Entry rules:**

* **Case 1**: `account_type = "trial"` AND `trial_end_date` within the next 7 days (the **Trial Expiring** segment from the strategy page, with the threshold widened from 3 days to 7).
* **Case 2**: Custom Event entry on `trial_started` with a Wait until 7 days before `trial_end_date` using [time operators](./time-operators). Cleaner attribution — entry timestamp matches the trial's actual start, not the moment OneSignal evaluated the segment.

**Exit rules:**

* **Both cases**: Exit when `trial_converted` Custom Event fires *or* `account_type = "paid"`. Whichever your billing pipeline updates first triggers the exit.
* **Both cases**: Also exit on `subscription_cancelled` so users who explicitly cancel their trial don't get further reminders.

**Re-entry rules:** No — most trials are single-use. Apps that offer trial extensions should set re-entry per business policy.

**Future additions only:** **Checked** — keep this on so users mid-trial when the Journey is created don't get bulk-enrolled into a 7-day cadence partway through.

### Trial-to-paid templates

| Days before end | Channel          | Purpose                                                                                             |
| :-------------: | ---------------- | --------------------------------------------------------------------------------------------------- |
|        7        | Push             | Soft heads-up. "Your trial ends `{{ days_remaining }}` days from now" framing.                      |
|        5        | Email            | Feature recap + upgrade CTA. Personalize the recap with what the user already used.                 |
|        3        | Push + IAM       | Activation-aware nudge. Deep-link to the upgrade screen, or the core feature for unactivated users. |
|        1        | Push             | Last call — concrete time remaining. "Your trial ends tomorrow at `{{ trial_end_time }}`."          |
|   0 (last day)  | SMS *(optional)* | High-intent SMS-opted-in users only. Keep copy short and include the upgrade deep link.             |

Keep every push and email deep-linked to the upgrade screen — see [Deep linking](./deep-linking). The fewer taps between message and conversion, the higher the rate.

<Tabs>
  <Tab title="Case 1 – Basic">
    The Basic flow uses the `trial_end_date` Tag with [time operators](./time-operators) to schedule each day's message a fixed number of days before the trial ends.

    <Steps>
      <Step title="Entry: Trial Expiring (widened to 7 days)">
        **Goal:** Catch every trial user 7 days before their trial ends.

        **Setup:** Build a segment `account_type = "trial"` AND `trial_end_date` within the next 7 days. Use this as the Journey entry audience.
      </Step>

      <Step title="Push: Day-7 heads-up">
        **Goal:** Open the Journey with a non-pushy reminder that the trial is ending.

        Lead with what the user has already done, not what they'll lose. "You've logged 6 sessions — keep your streak going past `{{ trial_end_date | date: '%b %-d' }}`" outperforms "Your trial expires soon."
      </Step>

      <Step title="Wait Until: 5 days before trial_end_date">
        **Goal:** Hold until the next milestone.

        Use a [time operator](./time-operators) Wait Until: `trial_end_date - 5 days`. This is more precise than a fixed 2-day Wait, because it anchors to the actual trial end date for users who entered partway through their trial.
      </Step>

      <Step title="Email: Day-5 feature recap and upgrade CTA">
        **Goal:** Surface a longer-form value reminder with a primary CTA to upgrade.

        Recap features used during the trial (where you have the data) and pair the recap with a single upgrade CTA. Email is where the longer-form value case lives — push and IAM carry the urgency.
      </Step>

      <Step title="Wait Until: 3 days before trial_end_date">
        **Goal:** Hold until the Day-3 push window.
      </Step>

      <Step title="Push: Day-3 nudge">
        **Goal:** Re-engage users who haven't yet upgraded.

        Reference time concretely: "3 days left." Deep-link to the upgrade screen.
      </Step>

      <Step title="Wait Until: 1 day before trial_end_date">
        **Goal:** Hold until the final-day push.
      </Step>

      <Step title="Push: Last call">
        **Goal:** A clear, concrete final ask.

        "Your trial ends tomorrow at `{{ trial_end_date | date: '%-l %p' }}`." Keep the copy tight; this is the highest-intent moment in the Journey.
      </Step>

      <Step title="Exit on trial_converted or account_type = paid">
        **Goal:** Stop the Journey the moment the user converts.

        Configure as a Journey-level exit rule, not a step. Exit also on `subscription_cancelled` so cancelled users don't continue to receive trial-ending reminders.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Case 2 – Advanced">
    Case 2 is structurally identical to Case 1, with two additions: an entry rule on `trial_started` (cleaner attribution than segment entry), and an activation branch that varies copy based on whether the user has hit `core_feature_used`.

    ##### Required data

    | Type            | Name                | Description                                                                                                            |
    | --------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
    | Tag (timestamp) | `trial_end_date`    | Unix timestamp set at trial start. Required for time-operator Waits.                                                   |
    | Tag (string)    | `account_type`      | `trial` / `paid` / `lapsed`. Drives Journey entry and exit.                                                            |
    | Tag (`1`)       | `core_feature_used` | Drives the activation branch. Users with the Tag get "keep going" copy; users without get "try the core feature" copy. |
    | Custom Event    | `trial_started`     | Journey entry event.                                                                                                   |
    | Custom Event    | `trial_converted`   | Exit event.                                                                                                            |

    ##### Activation branch

    The key Case 2 enhancement is a Yes/No branch on `core_feature_used` before each push, so the same Journey delivers two parallel cadences:

    * **Activated users** (`core_feature_used = "1"`): "You're on track — `{{ core_feature_name }}` is what most subscribers stay for. Keep going past `{{ trial_end_date | date: '%b %-d' }}`."
    * **Unactivated users** (Tag does not exist): "Try `{{ core_feature_name }}` before `{{ trial_end_date | date: '%b %-d' }}`. It's the part most subscribers love."

    Both branches deep-link to the upgrade screen; the activation branch additionally references the feature in a positive frame, while the unactivated branch deep-links to the core feature itself on the Day-3 push to give the user one last shot at activation before asking for the upgrade.

    ##### Day-by-day shape

    Identical to Case 1's step list, with the activation branch inserted before each push step (Day 7, Day 3, Day 1, and the optional Day 0 SMS). The Day-5 email gets the same activation-aware variation via Liquid conditionals — see [Liquid syntax](./using-liquid-syntax).
  </Tab>
</Tabs>

<Tip>
  **Measure trial-to-paid conversion rate by activation status.** Activated users should convert at materially higher rates than unactivated users. If they don't, the issue is upstream — your Welcome Journey isn't hitting the right Aha moment, not your conversion copy.
</Tip>

## Event-driven Journeys

Event-driven Journeys react to specific actions in real time. They're entirely Case 2 territory — you need [Custom Events](./custom-events) flowing into OneSignal to use them.

Common mobile-first event-driven patterns:

| Pattern                     | Trigger event                                                    | What the Journey does                                  |
| --------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------ |
| Purchase confirmation       | `purchase_completed`                                             | Thank-you push and email receipt with cross-sell items |
| Milestone celebration       | `level_completed`, `streak_reached`, `loyalty_milestone_reached` | Reward push referencing what was achieved              |
| Cart or upgrade abandonment | `checkout_started` (with Wait Until on `purchase_completed`)     | Recovery push on the expiration branch                 |
| Social moments              | `friend_invited`, `friend_joined`                                | Confirmation message and prompt for additional invites |
| Payment recovery (dunning)  | `payment_failed` (with Wait Until on `payment_succeeded`)        | Card-update push and email on the expiration branch    |

### Dunning: payment recovery

A representative event-driven shape that almost every subscription app needs. Failed renewal payments are involuntary churn — most users want to keep paying but have an expired card or a temporary decline. A short dunning Journey recovers the majority of these.

<Steps>
  <Step title="Entry: payment_failed Custom Event">
    **Goal:** Catch failed renewal charges the moment your billing platform reports them.

    Fire `payment_failed` from your backend with `failure_reason` (e.g., `card_expired`, `insufficient_funds`) as a property. Filter the entry rule to `account_type = "paid"` so failed checkout attempts on free accounts don't enter the dunning Journey.
  </Step>

  <Step title="Wait Until: payment_succeeded">
    **Goal:** Exit the Journey the moment the customer's card processes successfully.

    **Condition:** `payment_succeeded` Custom Event fires.
    **Expiration:** **48 hours** — most card retries succeed within 24–48 hours of the original failure.

    * **Event branch** — payment succeeded. Continue silently; no recovery message needed.
    * **Expiration branch** — payment still failing. Continue to the recovery push and email.
  </Step>

  <Step title="Push Notification: card-update reminder (expiration branch)">
    **Goal:** Surface a one-tap deep link to the billing or payment-method screen.

    Use Liquid to vary copy by `failure_reason` — "Your card on file expired" lands much better than a generic "We couldn't process your payment." Deep-link straight to the card-update screen so the user doesn't have to navigate.
  </Step>

  <Step title="Email: detailed dunning notice (expiration branch)">
    **Goal:** Give the customer a longer-form explanation and a self-serve recovery path.

    Send 1–2 hours after the push so they aren't simultaneous. Include the amount, the SKU or plan name, the failure reason, and a primary CTA to update payment. Email is also where the legal/billing detail lives that doesn't fit in a push.
  </Step>

  <Step title="Exit: subscription_cancelled or 7-day timeout">
    **Goal:** Stop dunning if the customer cancels or never recovers.

    Add an explicit **exit on `subscription_cancelled`** so a cancellation routes the user into the [Win-back Journey](#win-back-journeys) instead of continuing to receive payment-update reminders. After 7 days without success, the customer flips to `account_type = "lapsed"` via your billing webhook and exits naturally.
  </Step>
</Steps>

The full pattern guide — entry rules, Wait Until with Event Matching, threshold modeling, and Liquid personalization — lives on [Event-driven Journeys](./event-driven-journeys). For mobile-specific welcome and retention shapes that include event-driven steps, see Case 2 in the Welcome and Retention sections on this page.

<Card title="Event-driven Journeys" icon="bolt" href="./event-driven-journeys">
  Three end-to-end patterns plus the design rules for events, properties, and Wait Until matching.
</Card>

## Retention Journeys

Retention catches active users who are slipping before they fully disengage. Two triggers matter for mobile-first apps:

* **Inactivity** — the user hasn't opened the app in N days
* **Core feature dormancy** — the user is opening the app, but hasn't engaged with the core feature in N days (Case 2 only — needs the `core_feature_used` Tag)

<Note>
  Retention catches users who are still **active subscribers but slipping**. Once a user explicitly cancels or expires, they belong in the [Win-back Journey](#win-back-journeys), not this one. The two share Tags and segments but have different goals — retention prevents churn, win-back recovers churned users.
</Note>

### Retention Journey configuration

Both cases share segment shape; the difference is granularity.

**Entry trigger:**

* **Case 1**: Built-in segment `Last Session > 7 days`.
* **Case 2**: Combine `Last Session > 7 days` with `account_type` to split trial users (7 days) from paid users (14 days). Optionally add a parallel `Core feature untouched` segment for feature-dormant users.

**Cadence (both cases):**

| Day     | Channel | Purpose                                          |
| ------- | ------- | ------------------------------------------------ |
| 7 / 14  | Push    | Reminder of value the user has already seen      |
| 10 / 17 | Email   | Tip or use case the user may not have discovered |
| 14 / 21 | Push    | One clear action to bring them back              |

**Exit conditions:**

* **Both cases**: Auto-exit when the user opens the app (segment membership ends).
* **Case 2**: Also exit on `subscription_cancelled` event so the user moves to the win-back funnel without overlap.

<Tabs>
  <Tab title="Case 1 – Basic">
    The Basic retention flow uses OneSignal's built-in `Last Session` filter to target both trial and paying users with a single cadence.

    <Steps>
      <Step title="Entry: Last Session > 7 days segment">
        **Goal:** Catch users the moment they cross the inactivity threshold.

        Built-in segment using OneSignal's `Last Session` filter. Adjust the threshold based on your app's expected usage cadence — daily-use apps may want 3–4 days, weekly-use apps may want 10–14.
      </Step>

      <Step title="Push Notification: Day 7 — Value reminder">
        **Goal:** Surface a short, benefit-led nudge that reminds the user what they're missing.

        Lead with the benefit, not a feature list. "Your week-in-review is ready" outperforms "Don't forget to use feature X" because it implies the user has earned something. Deep-link directly to the relevant screen — see [Deep linking](./deep-linking).
      </Step>

      <Step title="Email: Day 10 — Feature tip">
        **Goal:** Re-engage users who didn't respond to the Day 7 push with a longer-form value message.

        Email lets you carry more context than push. Highlight a feature, walkthrough, or use case the user may not have discovered. Keep it scannable — most reads happen in under 30 seconds.
      </Step>

      <Step title="Push Notification: Day 14 — Final CTA">
        **Goal:** Send one last clear action before the user fully disengages.

        Lead with a concrete next step rather than a guilt trip. "Pick up where you left off" works better than "We miss you."
      </Step>

      <Step title="Exit: on app open">
        **Goal:** Remove returning users from the Journey automatically.

        Because the entry segment depends on `Last Session`, the user falls out of segment membership when they open the app — no explicit exit rule needed. Make sure the Journey's [re-entry rules](./journeys-settings#re-entry-rules) allow re-entry after exit so the next inactivity period catches them again.
      </Step>
    </Steps>

    <Tip>
      **Track 14-day return rate as the headline retention metric.** A return rate above 8% on the Day-14 push is healthy for most subscription apps; below 4%, the inactivity threshold or message copy needs tuning before the cadence itself is the problem.
    </Tip>
  </Tab>

  <Tab title="Case 2 – Advanced">
    The Advanced retention flow uses `account_type` to split cadences between trial and paying users.

    <Steps>
      <Step title="Entry: split by account_type">
        **Goal:** Apply different inactivity thresholds for trial users vs paying users.

        * **Trial users:** `account_type = "trial"` AND `Last Session > 7 days`. Trial users have a short window before churn — react fast.
        * **Paid users:** `account_type = "paid"` AND `Last Session > 14 days`. The longer window accounts for normal usage variation in users who have already converted.

        Build these as two separate segments and run two parallel Journeys, or build one Journey with two entry rules — whichever your team can maintain more easily.
      </Step>

      <Step title="Push Notification: Day 7 / 14 — Personalized reminder">
        **Goal:** Bring the user back with a message that references something they already care about.

        If you have a `most_used_feature` Tag, mention it specifically: "It's been a week since your last `{{ most_used_feature }}` session." This lands meaningfully better than generic "We miss you" copy.
      </Step>

      <Step title="Email: Day 10 / 17 — Feature highlight">
        **Goal:** Email users who didn't respond to push with a longer-form re-engagement message.

        Highlight a feature or use case they haven't explored. Pull from the same `most_used_feature` Tag for personalization — "You've mastered `{{ most_used_feature }}`. Here's what to try next."
      </Step>

      <Step title="Push Notification: Day 14 / 21 — Final CTA">
        **Goal:** Make a clear final ask before the user fully disengages.

        If the user is still in trial, reference time remaining via `trial_end_date` — "Only 3 days left to set up your account." Trial-aware copy materially outperforms generic re-engagement at the end of the funnel.
      </Step>

      <Step title="Exits: app open or subscription_cancelled">
        **Goal:** Remove returning users automatically and route churned users to the win-back funnel.

        * **App open:** segment membership ends naturally when the user opens the app.
        * **`subscription_cancelled` event:** explicit exit rule that moves the user out of retention and into win-back without overlap. See [Mobile-first strategy](./mobile-first#lifecycle-journeys) for the win-back playbook.
      </Step>
    </Steps>
  </Tab>
</Tabs>

<Tip>
  **Use SMS sparingly.** SMS is most effective for high-intent moments — trial expiration, payment failures, account verification. Avoid using it for general re-engagement; the cost-per-message is high and the unsubscribe risk is real.
</Tip>

### Core feature dormancy (Case 2 only)

A user who's actively opening the app but never touching the core feature is a different kind of risk than one who's just inactive. They've passed the install hurdle but stalled before activation — re-engagement copy aimed at "we miss you" misses the actual problem.

Use the **Core feature untouched** segment from [Mobile-first strategy](./mobile-first#segments) — `core_feature_used` does not exist — combined with `Last Session < 7 days` so you only message users who *are* opening the app.

<Steps>
  <Step title="In-App Message: Core feature pointer">
    **Goal:** When the user is in the app, redirect their attention to the core feature.

    The IAM should include:

    1. A single, specific benefit tied to the core feature ("Set up your first goal in 60 seconds").
    2. A primary CTA that **deep-links** to the core feature screen.
    3. A dismiss action that doesn't re-show on the same session.

    Target the IAM at *Segment is "Core feature untouched"* AND *Last Session \< 24 hours* so it shows on the user's next visit while the moment is still fresh.
  </Step>

  <Step title="Push Notification: Core feature CTA">
    **Goal:** Catch the user when they're outside the app.

    Send a single push that deep-links to the core feature, no more than once per dormancy period. If they still haven't engaged after 7 more days, they fall into the inactivity track above naturally — don't double up.
  </Step>
</Steps>

## Win-back Journeys

Win-back targets users who have **already churned** — cancelled, lapsed, or expired — and aims to bring them back. The audience and goal are different from Retention: retention prevents churn for users still on a paid plan, while win-back recovers users who are no longer paying. Run them as separate Journeys with different exit conditions so a user can't sit in both at once.

Three principles drive the structure:

1. **Lead with value, not discount.** A "what you've been missing" message converts higher than a generic discount the first time. Reserve discounts for the second touch — discounting on day one trains users to cancel and wait for a coupon.
2. **Cap the active window.** Run win-back for 60 days post-cancellation, then exit users to a long-tail dormant list. Past 60 days, the unsubscribe risk outweighs the recovery upside.
3. **Differentiate by past value.** A high-LTV churned user (former VIP, long tenure, high spend) deserves a richer offer than a one-month trial that lapsed. Case 2 splits the cadence by `total_spend_cents` or tenure.

### Win-back configuration

**Entry rules:**

* **Case 1**: Built-in segment `account_type = "lapsed"`. Most billing pipelines flip the Tag from `paid` to `lapsed` on the cancellation date or at the end of the paid period.
* **Case 2**: Custom Event entry on `subscription_cancelled`, with a 7-day Wait before the first message. The 7-day buffer lets the immediate-regret cancellations pass — those users either resubscribe on their own or were never going to come back.

**Exit rules:**

* **Both cases**: Exit when `account_type` returns to `paid`, OR when `trial_started` or `subscription_renewed` Custom Event fires. Any of these mean the user is back; stop messaging.
* **Both cases**: Hard exit at 60 days post-entry. Configure as a final step or a Journey-level timeout — depending on your dashboard, this might be a `Wait: 60 days` followed by a Tag User step that adds `winback_completed = "1"` so the user can be excluded from re-entry.

**Re-entry rules:** Allow re-entry — a user who churns, returns, then churns again should re-enter the Journey on the new cancellation.

### Win-back templates

| Day | Channel | Purpose                                                                                                  |
| :-: | ------- | -------------------------------------------------------------------------------------------------------- |
|  7  | Email   | "Here's what you've been missing." Value reminder, no discount, no urgency.                              |
|  14 | Push    | One specific feature or content drop the user hasn't seen. Deep-link to it.                              |
|  30 | Email   | Return offer — first month at 50%, free trial extension, or feature unlock. First time discount appears. |
|  45 | Push    | Final low-key reminder of the offer. Use `{{ first_name \| default: 'there' }}` to personalize.          |
|  60 | (none)  | Hard exit. User flips to `winback_completed`.                                                            |

<Tabs>
  <Tab title="Case 1 – Basic">
    The Basic flow uses `account_type = "lapsed"` and a fixed 7 / 14 / 30 / 45 / 60-day cadence. Suitable for any app where your billing webhook updates `account_type` on cancellation.

    <Steps>
      <Step title="Entry: account_type = lapsed">
        **Goal:** Catch every lapsed user automatically the moment your billing pipeline flips the Tag.

        **Setup:** Built-in segment using the `account_type` Tag. Confirm your billing webhook fires reliably on the cancellation date or at the end of the paid period — without that, the Journey won't have an audience.
      </Step>

      <Step title="Wait: 7 days">
        **Goal:** Hold the first touch a week post-cancellation.

        Skipping the first week filters out immediate-regret cancellations and avoids the "we just got your cancellation" tone that reads as either tone-deaf or pushy.
      </Step>

      <Step title="Email: Day 7 — What you've been missing">
        **Goal:** A value reminder, no discount.

        Recap recent product updates, content drops, or feature releases since the user cancelled. Keep the tone neutral and informative — the user hasn't asked to be back yet.
      </Step>

      <Step title="Wait: 7 days (to Day 14)">
        **Goal:** Hold until the Day 14 push.
      </Step>

      <Step title="Push: Day 14 — Specific feature">
        **Goal:** Surface one feature or content drop tailored to the user's past usage.

        Deep-link directly to the relevant screen behind the auth wall — the user will hit a "log in or restart subscription" prompt, which is exactly the moment for an upgrade.
      </Step>

      <Step title="Wait: 16 days (to Day 30)">
        **Goal:** Hold until the offer touch.
      </Step>

      <Step title="Email: Day 30 — Return offer">
        **Goal:** First time the Journey introduces a discount or extension.

        Common offers: first month at 50%, two-week free trial extension, or a feature unlock. Whatever you choose, make the offer concrete, time-bound (e.g., expires in 14 days), and deep-link to a one-tap restart flow.
      </Step>

      <Step title="Wait: 15 days (to Day 45)">
        **Goal:** Hold until the final reminder.
      </Step>

      <Step title="Push: Day 45 — Final reminder">
        **Goal:** Low-key nudge that the offer is still available.

        "Your offer expires in `{{ days_remaining }}` days" works if your billing system supports a unique offer per user. Otherwise, a simple "We're still saving your spot" is fine.
      </Step>

      <Step title="Exit: 60 days or account_type = paid">
        **Goal:** Stop the Journey.

        Configure two exits:

        * **Conversion exit**: `account_type = "paid"` OR `subscription_renewed` event fires. The user is back; stop messaging.
        * **Timeout exit**: 60 days from entry. Set a `winback_completed = "1"` Tag in a Tag User step before exit so re-entry rules can exclude these users from accidental re-entry.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Case 2 – Advanced">
    Case 2 splits the cadence by past customer value. High-LTV churned users get a richer offer earlier; low-LTV users get the standard cadence. The structure is the same; the offer and channel mix differ.

    ##### Required data

    | Type            | Name                                     | Description                                                                                             |
    | --------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |
    | Tag (string)    | `account_type`                           | Set to `lapsed` on cancellation by your billing webhook. Drives entry.                                  |
    | Tag (integer)   | `total_spend_cents`                      | Lifetime value to date. Drives the high-LTV branch.                                                     |
    | Tag (timestamp) | `subscription_start_date`                | Used to compute tenure for tenure-based segmentation.                                                   |
    | Custom Event    | `subscription_cancelled`                 | Optional Journey entry event. Carry `cancel_reason` as a property if your billing platform captures it. |
    | Custom Event    | `subscription_renewed` / `trial_started` | Exit events for users who return on their own or via the Journey.                                       |

    ##### High-LTV vs standard branches

    After the Day-7 entry Wait, branch on `total_spend_cents` (or your equivalent LTV threshold):

    * **High-LTV branch** (`total_spend_cents > 5000` — adjust to your business): Day 7 personal email referencing tenure ("You were with us for `{{ tenure_months }}` months"). Day 14 push with a tier-specific concierge offer (priority support, free upgrade, account-manager outreach). Day 30 offer is more generous than the standard branch — e.g., 3 free months or a year-over-year price match.
    * **Standard branch** (`total_spend_cents <= 5000`): Identical to Case 1's cadence — Day 7 value email, Day 14 feature push, Day 30 offer, Day 45 reminder, Day 60 exit.

    ##### `cancel_reason`-aware copy

    If `subscription_cancelled` carries a `cancel_reason` property (e.g., `too_expensive`, `not_using`, `missing_feature`), branch the Day-7 email on that property:

    * `too_expensive`: Lead the Day-30 offer with the discount.
    * `not_using`: Day 7 leads with the most-valuable feature the user *did* use; Day 14 surfaces a feature drop.
    * `missing_feature`: Reference the feature directly. If it's now shipped, lead with that. If it isn't, route to a long-tail "we'll let you know when this ships" list and exit the standard win-back Journey early.

    See [Personalization with Custom Events](./personalization-custom-event) for the Liquid pattern.
  </Tab>
</Tabs>

<Tip>
  **Win-back rate is a lagging indicator.** A churned user who returns within 60 days is what most teams measure, but a user who returns 9 months later via a separate channel (paid acquisition, organic search, referral) is also a win-back success even though no Journey claims credit. Track both — the Journey-attributed win-back rate and the overall churned-cohort return rate at 90 / 180 / 365 days.
</Tip>

## FAQ

### Should I build all five Journeys at once?

No — sequence them by leverage and data readiness:

1. **Welcome Journey (Case 1)** first — highest leverage on long-term retention, requires no Custom Events.
2. **Retention Journey (Case 1)** once you have at least 30 days of session data so you can tune the inactivity threshold.
3. **Trial-to-paid conversion** as soon as `trial_end_date` is reliably set on every trial user.
4. **Win-back Journey** once your billing pipeline reliably flips `account_type` to `lapsed` on cancellation.
5. **Event-driven Journeys** (purchase, milestone, dunning) as your Custom Event tracking matures.

Each new Journey should reference the exit conditions of the others so users don't sit in two at once — see the next FAQ.

### How do I prevent a user from receiving overlapping Journeys?

Use exit conditions that map to Journey progression:

* **Welcome** exits when `core_feature_used = "1"` or `onboarding_completed`.
* **Trial-to-paid** exits when `trial_converted` or `account_type = "paid"`. Also exits on `subscription_cancelled` so cancelled trials don't keep getting reminders.
* **Retention** only enters when `Last Session > 7 days` AND `account_type` is `trial` or `paid`. Exits on app open or `subscription_cancelled`.
* **Win-back** only enters when `account_type = "lapsed"` (or on `subscription_cancelled` event). Exits on `account_type = "paid"`, `subscription_renewed`, or 60-day timeout.

Re-entry rules on Journey settings prevent the same user from re-entering an active flow. See [Re-entry rules](./journeys-settings#re-entry-rules).

### Can I build these Journeys without Custom Events or Tags?

Yes — the Case 1 versions of Welcome, Retention, and Win-back use only built-in segments (`First Session`, `Last Session`, `account_type`) and require no Custom Events. Trial-to-paid requires the `trial_end_date` Tag in both cases. Event-driven Journeys (purchase confirmation, milestones, dunning) all require Custom Events.

### Where should email warm-up fit in the rollout?

Before sending any Welcome or Retention email at scale on a new sending domain. See [Email warm-up](./email-warm-up). For double opt-in flows that confirm subscribers before adding them to your sending list, see [Set up confirmed opt-in for email](./double-opt-in-email).

### How frequently should I message users in onboarding?

A common starting point is one message per day for the first 3 days, then drop to every 2–3 days. Watch unsubscribe rate and push opt-out rate at each step — those tell you sooner than open rate whether you're being too aggressive.

### Do I need to use all three channels (push, email, SMS)?

No. Push and in-app are the foundation for mobile-first. Email layers in for consenting users when you have longer-form content (welcome, weekly digest, conversion). SMS is reserved for urgent, high-intent moments. Most apps run effectively on push + email.

## Related pages

<Columns cols={2}>
  <Card title="Mobile-first strategy" icon="mobile" href="./mobile-first">
    Set up External IDs, Tags, and segments that power lifecycle Journeys.
  </Card>

  <Card title="Event-driven Journeys" icon="bolt" href="./event-driven-journeys">
    Three end-to-end patterns for translating real behavior into automation.
  </Card>

  <Card title="Welcome Journey: Mobile gaming" icon="gamepad" href="./welcome-journey-mobile-gaming">
    Worked example of a 7-day onboarding Journey with both basic and advanced data paths.
  </Card>

  <Card title="Set up confirmed opt-in for email" icon="envelope-circle-check" href="./double-opt-in-email">
    Implement double opt-in to protect deliverability before email blasts.
  </Card>

  <Card title="Email warm-up" icon="envelope" href="./email-warm-up">
    Ramp volume on a new sending domain without hurting deliverability.
  </Card>

  <Card title="Segments" icon="users" href="./segmentation">
    Group users by shared characteristics to target Journeys.
  </Card>
</Columns>
