> ## 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.

# Send a welcome email from Auth0

> Call Courier from an Auth0 post-registration Action, with no webhook route to host.

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 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, Users" />

Auth0 runs your code itself, so this one needs no webhook route. A **post-user-registration** Action calls Courier straight from Auth0.

## What you will build

```mermaid theme={null}
flowchart LR
    A["User registers"] --> B["Auth0 Action"]
    B --> C["Create profile"]
    C --> D["Welcome email"]
```

## Prerequisites

* <AppLink href="https://app.courier.com/~/test/platform/api-keys">A Courier Test API key</AppLink>
* An Auth0 tenant with a Database or Passwordless connection
* A published <Doc href="/docs/design/templates/overview">template</Doc> to send

## How this differs from a webhook

Clerk and Stripe post to a route you host, so you verify a signature and run your own code. Auth0 Actions are your code, running inside Auth0. Three consequences follow.

**No route, no signature, no tunnel.** There is nothing public to host and nothing to verify, because nothing crossed the internet to reach you. Local development needs no `ngrok`.

**Secrets live in the Action.** Auth0 stores them and exposes them as `event.secrets`, so your Courier key never sits in your app's environment.

**It is non-blocking, and that cuts both ways.** Auth0 does not wait for the Action to finish, so a slow or failing Action never blocks a signup. It also means a failure is invisible unless you look. Auth0's own logs are where you find it.

## Set it up

<Steps>
  <Step title="Create the Action">
    In the Auth0 Dashboard, go to **Actions → Library**, select **Build Custom**, and create an Action on the **Post User Registration** trigger.
  </Step>

  <Step title="Add the SDK and your key">
    In the Action editor, open the **Dependencies** panel in the left sidebar and add `@trycourier/courier`. Actions install npm packages into the runtime, so the <Doc href="/docs/sdk-libraries/node">Node SDK</Doc> works here exactly as it does on your own server.

    Then open the key icon in the same sidebar and add a secret named `COURIER_API_KEY` with your Courier key as its value.

    Auth0 exposes the secret to the Action as `event.secrets.COURIER_API_KEY`. It is never in your application's environment and never in the Action's source.
  </Step>

  <Step title="Write the Action">
    The Action receives the newly created user on `event.user`. Create the profile, then send.

    ```javascript Post User Registration lines theme={null}
    const Courier = require("@trycourier/courier");

    exports.onExecutePostUserRegistration = async (event) => {
      const courier = new Courier({ apiKey: event.secrets.COURIER_API_KEY });
      const { user_id, email, given_name } = event.user;

      // Auth0's user_id becomes the Courier user id, so both systems agree.
      await courier.profiles.create(user_id, {
        profile: { email, given_name },
      });

      await courier.send.message({
        message: {
          to: { user_id },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          data: { name: given_name ?? "there" },
        },
      });
    };
    ```

    Actions are CommonJS, so the SDK comes in with `require` rather than `import`. Everything else is the same code you would write on your own server, and the <Doc href="/docs/sdk-libraries/node">Node SDK reference</Doc> applies unchanged.

    <Warning>
      Auth0 does not wait for this Action, so a failed send does **not** fail the signup and does **not** surface to the user. Wrap the calls in `try`/`catch` and `console.log` the error, then read it in **Monitoring → Logs**. Without that, a broken welcome email is silent.
    </Warning>
  </Step>

  <Step title="Deploy and add it to the flow">
    Select **Deploy**, then go to **Actions → Triggers → post-user-registration** and drag the Action into the flow between Start and Complete. An Action that is deployed but not in the flow never runs, which is the most common reason nothing happens.
  </Step>
</Steps>

## Verify

<Steps>
  <Step title="Register a test user">
    Create a user through your app's signup, or from **User Management → Users** in the Auth0 Dashboard. The trigger fires for Database and Passwordless connections.
  </Step>

  <Step title="Check the Auth0 log">
    In **Monitoring → Logs**, find the registration event. A failing Action shows up here and nowhere else.
  </Step>

  <Step title="Confirm the send">
    Open <AppLink href="https://app.courier.com/logs">Logs</AppLink> in Courier and confirm the message.
  </Step>
</Steps>

<Note>
  The trigger fires for **Database and Passwordless connections only**. A user arriving through a social or enterprise connection does not fire it, so a Google sign-up sends nothing. For those, use a post-login Action and check whether the login is the user's first.
</Note>

## Adapt it for other triggers

| Auth0 trigger            | What to send                                   |
| :----------------------- | :--------------------------------------------- |
| `post-user-registration` | Welcome, for database and passwordless signups |
| `post-login`             | A new-device or new-location alert             |
| `send-phone-message`     | Route Auth0's own MFA codes through Courier    |
| `post-change-password`   | A password-changed confirmation                |

`post-login` runs on every login, so gate anything you send there on a condition. Sending on each login is how a welcome email becomes a complaint.
