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

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message`, the default import of the v7 Node SDK. Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Build an onboarding sequence

> Welcome new users over a week with delays and a branch that skips those who activated.

export const Tags = ({items}) => {
  const routes = {
    Email: "/integrations/email/overview",
    SMS: "/integrations/sms/overview",
    Push: "/integrations/push/overview",
    Inbox: "/in-app/overview",
    Chat: "/integrations/direct-message/overview",
    Templates: "/design/templates/overview",
    Variables: "/design/templates/variables",
    Elemental: "/design/elemental/overview",
    Brands: "/design/brands",
    Translations: "/design/elemental/locales",
    Routing: "/send/routing",
    Preferences: "/recipients/preferences/overview",
    Journeys: "/journeys/overview",
    Broadcasts: "/broadcasts/overview",
    Tenants: "/tenants/overview",
    Logs: "/monitor/overview",
    Webhooks: "/monitor/webhooks/outbound",
    Lists: "/recipients/lists-and-audiences/overview",
    Users: "/recipients/overview",
    Digests: "/journeys/nodes/digest",
    Environments: "/workspaces/overview",
    MCP: "/resources/mcp"
  };
  const icons = {
    Email: "envelope",
    SMS: "comment",
    Push: "mobile",
    Inbox: "inbox",
    Chat: "comments",
    Templates: "pen-ruler",
    Variables: "pen-ruler",
    Elemental: "pen-ruler",
    Brands: "pen-ruler",
    Translations: "pen-ruler",
    Routing: "paper-plane",
    Preferences: "users",
    Journeys: "route",
    Broadcasts: "bullhorn",
    Tenants: "building",
    Logs: "chart-simple",
    Webhooks: "chart-simple",
    Lists: "users",
    Users: "users",
    Digests: "route",
    Environments: "briefcase",
    MCP: "toolbox"
  };
  const base = "https://d3gk2c5xim1je2.cloudfront.net/fontawesome/v7.2.0/regular/";
  const names = String(items || "").split(",").map(entry => entry.trim()).filter(Boolean);
  return <div className="cx-tags">
      {names.map(name => {
    const href = routes[name];
    const icon = icons[name];
    const url = icon ? "url(" + base + icon + ".svg)" : null;
    const style = url ? {
      "--cx-tag-icon": url
    } : null;
    if (!href) {
      return <span className="cx-tag" data-icon={icon} style={style} key={name}>
              {name}
            </span>;
    }
    return <a className="cx-tag" data-icon={icon} style={style} href={href} key={name}>
            {name}
          </a>;
  })}
    </div>;
};

export const AppLink = ({href, children, name, bare}) => {
  const label = children || name || "Open in Courier";
  if (bare) {
    return <a href={href} target="_blank" rel="noreferrer">{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="app" href={href} target="_blank" rel="noreferrer">
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method" aria-hidden="true">↗</span>
    </a>;
};

export const Endpoint = ({method, path, name, href, children, bare}) => {
  const verb = String(method || "").toUpperCase();
  const title = verb + " " + path;
  const label = children || name || path;
  if (bare) {
    return href ? <a href={href}><code>{title}</code></a> : <code>{title}</code>;
  }
  if (!href) {
    return <span className="cx-endpoint" data-method={verb} title={title}>
        <span className="cx-endpoint-label">{label}</span>
        <span className="cx-endpoint-method">{verb}</span>
      </span>;
  }
  return <a className="cx-endpoint" data-method={verb} href={href} title={title}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">{verb}</span>
    </a>;
};

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

<Tags items="Email, Journeys" />

Walk a new user through their first week with one Journey: welcome them, wait, then nudge only the people who stalled.

A Journey is a graph that runs once per user. Each run carries that user's data, so the whole sequence is one API call from your signup code.

## What you will build

```mermaid theme={null}
flowchart LR
    A["Signup"] --> B["Welcome email"]
    B -->|3 days| C{"Activated?"}
    C -->|No| D["Tip email"]
    D -->|4 days| E["Nudge"]
    C -->|Yes| F["No more emails"]
```

## Prerequisites

* <Doc href="/docs/integrations/email/overview">A connected email provider</Doc>
* <AppLink href="https://app.courier.com/settings/api-keys">A Courier API key</AppLink>
* A signup event from your app

## Build the sequence

<Steps>
  <Step title="Create the Journey">
    Open <AppLink href="https://app.courier.com/orchestration/journeys">Journeys</AppLink>, select **New Journey**, name it `user-onboarding`, and choose the **API Invoke** trigger so your signup code starts each run.

    <Frame>
      <img src="https://mintcdn.com/courier-4f1f25dc/9rcgucLA9fBnJt_U/assets/journey-new-journey-trigger.webp?fit=max&auto=format&n=9rcgucLA9fBnJt_U&q=85&s=7c29c7f4a5f95ec0bb49a95a3b455707" alt="The New Journey dialog with a name field and four trigger options" className="mx-auto" style={{ width:"72%" }} width="1740" height="1410" data-path="assets/journey-new-journey-trigger.webp" />
    </Frame>
  </Step>

  <Step title="Send the welcome message">
    Drop a **send** node on the canvas, pick the email channel, and select **+ Create** to write the content inline. Reference the run's data with variables such as `{{first_name}}`.

    Each send node owns one template scoped to this Journey, so onboarding content stays out of your workspace template list.
  </Step>

  <Step title="Wait three days">
    Drag a **delay** node after the send and set it to three days. The run parks there and resumes on its own, so you hold no timers in your code.

    A delay is measured from when the run reaches the node, not from when the Journey started.
  </Step>

  <Step title="Branch on whether they activated">
    Add a **branch** node after the delay and write a condition against the run's data, for example `data.has_activated`. Wire the true path straight to an end, and the false path onward.

    The branch reads the data the run is carrying. To decide on something that changed *during* the three-day wait, add a **fetch data** node before the branch and call your own API for the current value.
  </Step>

  <Step title="Add the follow-ups">
    On the false path, add a send node with a tip message, another **delay** of four days, and a final nudge. Only users who never activated reach these nodes.
  </Step>

  <Step title="Set a cancelation token">
    In the journey's settings, set the **cancelation token** to `onboarding-{{profile.id}}`. Courier interpolates it when each run starts, so every user's run carries its own token and you can stop one user's sequence without touching anyone else's.

    A token that references a variable missing at run start leaves the run untagged. It is still cancelable by run ID, but not by token. <Doc href="/docs/journeys/nodes/cancel#cancel-a-group-with-a-cancellation-token">Cancellation tokens</Doc> covers how the shape of the token decides what it covers.
  </Step>

  <Step title="Publish">
    Select **Publish** to lock the draft as a version, then copy the Journey ID. New runs use the published version. Runs in flight finish on the version they started with.
  </Step>

  <Step title="Invoke it from your signup code">
    Call <Endpoint method="POST" path="/journeys/{templateId}/invoke" name="Invoke a Journey" href="/docs/api-reference/journeys/invoke-a-journey" /> once per new user. The run picks up the cancelation token from the journey's settings, so invoke carries only the user and the data the branch reads.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const journeysInvokeResponse = await client.journeys.invoke('user-onboarding', {
        user_id: 'user_123',
        data: { first_name: 'Sarah', has_activated: false },
      });
      ```

      ```python Python theme={null}
      journeys_invoke_response = client.journeys.invoke(
          template_id="user-onboarding",
          user_id="user_123",
          data={"first_name": "Sarah", "has_activated": False},
      )
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.courier.com/journeys/user-onboarding/invoke \
        -H "Authorization: Bearer $COURIER_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: signup-user_123" \
        -d '{
          "user_id": "user_123",
          "data": { "first_name": "Sarah", "has_activated": false }
        }'
      ```

      ```ruby Ruby theme={null}
      journeys_invoke_response = courier.journeys.invoke(
        "user-onboarding",
        user_id: "user_123",
        data: {first_name: "Sarah", has_activated: false}
      )
      ```

      ```go Go theme={null}
      journeysInvokeResponse, err := client.Journeys.Invoke(
      	context.TODO(),
      	"user-onboarding",
      	courier.JourneyInvokeParams{
      		JourneysInvokeRequest: courier.JourneysInvokeRequestParam{
      			UserID: courier.String("user_123"),
      			Data: map[string]any{
      				"first_name":    "Sarah",
      				"has_activated": false,
      			},
      		},
      	},
      )
      ```

      ```java Java theme={null}
      JourneyInvokeParams params = JourneyInvokeParams.builder()
          .templateId("user-onboarding")
          .journeysInvokeRequest(JourneysInvokeRequest.builder()
              .userId("user_123")
              .data(JourneysInvokeRequest.Data.builder()
                  .putAdditionalProperty("first_name", JsonValue.from("Sarah"))
                  .putAdditionalProperty("has_activated", JsonValue.from(false))
                  .build())
              .build())
          .build();
      JourneysInvokeResponse journeysInvokeResponse = client.journeys().invoke(params);
      ```

      ```php PHP theme={null}
      $journeysInvokeResponse = $client->journeys->invoke(
        'user-onboarding',
        userID: 'user_123',
        data: ['first_name' => 'Sarah', 'has_activated' => false],
      );
      ```

      ```csharp C# theme={null}
      JourneyInvokeParams parameters = new()
      {
          TemplateID = "user-onboarding",
          UserID = "user_123",
          Data = new Dictionary<string, JsonElement>()
          {
              { "first_name", JsonSerializer.SerializeToElement("Sarah") },
              { "has_activated", JsonSerializer.SerializeToElement(false) },
          },
      };

      var journeysInvokeResponse = await client.Journeys.Invoke(parameters);
      ```

      ```bash CLI theme={null}
      courier journeys invoke \
        --api-key "$COURIER_API_KEY" \
        --template-id user-onboarding \
        --user-id user_123 \
        --data '{"first_name":"Sarah","has_activated":false}'
      ```

      ```text MCP theme={null}
      With Courier MCP, start the user-onboarding journey for user_123 with first_name Sarah.
      ```
    </CodeGroup>

    Send an `Idempotency-Key` header so a retried signup request returns the same run instead of starting a second sequence.
  </Step>

  <Step title="Stop the sequence when they activate">
    When the user finally activates, <Endpoint method="POST" path="/journeys/cancel" name="Cancel Journey runs" href="/docs/api-reference/journeys/cancel-journey-runs" /> stops the run by the token the journey stamped on it. Anything already delivered stays delivered, and the pending steps never fire.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const cancelJourneyResponse = await client.journeys.cancel({ cancelation_token: 'onboarding-user_123' });
      ```

      ```python Python highlight={2} theme={null}
      cancel_journey_response = client.journeys.cancel(
          cancelation_token="onboarding-user_123",
      )
      ```

      ```bash cURL highlight={5} theme={null}
      curl -X POST https://api.courier.com/journeys/cancel \
        -H "Authorization: Bearer $COURIER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "cancelation_token": "onboarding-user_123"
        }'
      ```

      ```ruby Ruby theme={null}
      cancel_journey_response = courier.journeys.cancel(cancel_journey_request: {cancelation_token: "onboarding-user_123"})
      ```

      ```go Go highlight={3-4} theme={null}
      cancelJourneyResponse, err := client.Journeys.Cancel(context.TODO(), courier.JourneyCancelParams{
      	CancelJourneyRequest: courier.CancelJourneyRequestUnionParam{
      		OfByCancelationToken: &courier.CancelJourneyRequestByCancelationTokenParam{
      			CancelationToken: "onboarding-user_123",
      		},
      	},
      })
      ```

      ```java Java highlight={1-2} theme={null}
      CancelJourneyRequest.ByCancelationToken params = CancelJourneyRequest.ByCancelationToken.builder()
          .cancelationToken("onboarding-user_123")
          .build();
      CancelJourneyResponse cancelJourneyResponse = client.journeys().cancel(params);
      ```

      ```php PHP highlight={2} theme={null}
      $cancelJourneyResponse = $client->journeys->cancel(
        cancelationToken: 'onboarding-user_123'
      );
      ```

      ```csharp C# highlight={3} theme={null}
      JourneyCancelParams parameters = new()
      {
          CancelJourneyRequest = new ByCancelationToken("onboarding-user_123")
      };

      var cancelJourneyResponse = await client.Journeys.Cancel(parameters);
      ```

      ```bash CLI highlight={3} theme={null}
      courier journeys cancel \
        --api-key "$COURIER_API_KEY" \
        --cancelation-token onboarding-user_123
      ```

      ```text MCP theme={null}
      With Courier MCP, cancel the journey run with the cancelation token onboarding-user_123.
      ```
    </CodeGroup>

    To cancel one run instead, store the `runId` that invoke returned and pass `run_id` in place of `cancelation_token`. That branch answers with the run's resulting `status`, so you can assert the cancel landed. Cancelling by token echoes the token back and reports nothing about what it matched.
  </Step>
</Steps>

## Verify

<Steps>
  <Step title="Watch a run take the right path">
    Invoke for a test user with `has_activated: false` and open the Journey's **Logs** tab. Click the run to see each node's outcome drawn on the graph, and confirm the branch took the false path.
  </Step>

  <Step title="Prove the delay parks the run">
    The run should sit at the delay node rather than completing. Shorten the delay to a few minutes on a test copy if you would rather not wait three days.
  </Step>

  <Step title="Confirm a cancel stops it">
    Cancel the token while a run waits at a delay, then confirm the run reads `Canceled` and no further message is delivered.
  </Step>
</Steps>

<Note>
  An unpublished draft has no active version, so invoking it does nothing. Publish first, then invoke.
</Note>

Every node type, and what each one accepts, is in the <Doc href="/docs/journeys/nodes">nodes reference</Doc>.
