> ## 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 message from Firebase Auth

> Send a welcome message from a Cloud Function on the Firebase user-created event.

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

Firebase Auth has no webhook. It runs your code instead, as a Cloud Function on the user-created event.

## What you will build

```mermaid theme={null}
flowchart LR
    A["User signs up"] --> B["Cloud Function"]
    B --> C["Create profile"]
    C --> D["Welcome message"]
```

## Prerequisites

* <AppLink href="https://app.courier.com/~/test/platform/api-keys">A Courier Test API key</AppLink>
* A Firebase project with Authentication enabled, and the Firebase CLI
* A published <Doc href="/docs/design/templates/overview">template</Doc> to send

## Use the non-blocking trigger

Firebase gives you two ways to run code when a user is created, and for a welcome message the older one is the right one.

**`functions.auth.user().onCreate()`** is a 1st-gen trigger. It runs after the user exists, asynchronously, and nothing you do in it can affect the signup. There is no 2nd-gen equivalent, and Firebase says so plainly: 2nd gen does not support Authentication triggers. The two generations coexist in one file, so this is not a reason to avoid 2nd gen elsewhere.

**`beforeUserCreated`** is the 2nd-gen blocking function, and it is the wrong tool here for three reasons:

* It **requires upgrading the project** to Firebase Authentication with Identity Platform.
* It **must respond within 7 seconds**, after which Firebase returns an error and the client operation fails. A slow send would block a signup.
* Deleting the function without unregistering its trigger **prevents all users from authenticating**.

Blocking is for deciding whether someone may sign up. Sending a welcome is not that, and a notification outage should never stop a registration.

## Set it up

<Steps>
  <Step title="Install the dependencies">
    In your functions directory:

    ```bash theme={null}
    npm install firebase-functions firebase-admin @trycourier/courier
    ```
  </Step>

  <Step title="Store your Courier key as a secret">
    Firebase keeps secrets in Cloud Secret Manager and injects them at runtime.

    ```bash theme={null}
    firebase functions:secrets:set COURIER_API_KEY
    ```

    Bind it to the function that needs it. Secret values are hidden until the function runs, so they cannot be read at deploy time.
  </Step>

  <Step title="Write the function">
    The handler receives a `UserRecord`. Its `uid`, `email`, and `displayName` are the three fields worth reading here.

    ```javascript functions/index.js lines theme={null}
    const functions = require("firebase-functions/v1");
    const Courier = require("@trycourier/courier");

    exports.sendWelcome = functions
      .runWith({ secrets: ["COURIER_API_KEY"] })
      .auth.user()
      .onCreate(async (user) => {
        const { uid, email, displayName } = user;

        if (!email) return; // phone-only and anonymous signups have none

        const courier = new Courier({ apiKey: process.env.COURIER_API_KEY });

        // Firebase's uid becomes the Courier user id, so both systems agree.
        await courier.profiles.create(uid, {
          profile: { email, name: displayName },
        });

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

    The `runWith` call is what grants access to the secret, which then arrives on `process.env`. Importing from `firebase-functions/v1` explicitly keeps this working alongside 2nd-gen functions in the same file.

    <Warning>
      Anonymous and phone-only signups have no `email`, and the trigger fires for both. Returning early is what stops a crash on the first anonymous session.
    </Warning>
  </Step>

  <Step title="Deploy">
    ```bash theme={null}
    firebase deploy --only functions:sendWelcome
    ```
  </Step>
</Steps>

## Verify

<Steps>
  <Step title="Create a user">
    Sign up through your app, or add one from **Authentication → Users** in the Firebase console.
  </Step>

  <Step title="Read the function log">
    ```bash theme={null}
    firebase functions:log --only sendWelcome
    ```

    The trigger is asynchronous, so a failure appears here and nowhere else. It will not surface to the user.
  </Step>

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

## What the trigger covers

More than Auth0's equivalent, which is worth knowing if you are comparing the two.

| Sign-up route                                            | Fires                |
| :------------------------------------------------------- | :------------------- |
| Email and password                                       | Yes                  |
| A federated provider, including Google, on first sign-in | Yes                  |
| Anonymous session                                        | Yes, with no `email` |
| Created through the Admin SDK                            | Yes                  |
| A custom token                                           | **No**               |

The custom-token gap is the one to plan around. If you mint your own tokens for an existing identity system, this trigger never runs, and the profile write belongs wherever you create that user instead.
