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

# Add the Courier Inbox

> Install the SDK, sign the user in, and render the inbox component on any platform.

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>;
};

**Try it live:**

<CardGroup cols={2}>
  <Card title="Inbox" icon="play" href="https://inbox-demo.courier.com/inbox-demo">
    The full inbox component.
  </Card>

  <Card title="Popup menu" icon="play" href="https://inbox-demo.courier.com/inbox-demo?layout=courier-inbox-popup-menu">
    The inbox as an icon-button popup.
  </Card>
</CardGroup>

<Frame caption="Both layouts in the default theme: the full inbox on the left, the popup's icon button on the right.">
  <img src="https://mintcdn.com/courier-4f1f25dc/9rcgucLA9fBnJt_U/assets/inbox-split.webp?fit=max&auto=format&n=9rcgucLA9fBnJt_U&q=85&s=e6779c156f90b270f34f4fedf5d9e3fa" alt="Left, the Courier Inbox as a full-height panel with an unread count in its header, listing notifications with unread dots and per-message action buttons. Right, the same inbox collapsed to its icon button with an unread dot." className="mx-auto" width="3152" height="1776" data-path="assets/inbox-split.webp" />
</Frame>

Your backend generates the `jwt` in these snippets with your Courier API key. For the full flow, scope strings, and token refresh, see <Doc href="/docs/in-app/authenticate-users">Authenticate users</Doc>.

## Prerequisites

* <AppLink href="https://app.courier.com/signup">A Courier account</AppLink>
* <AppLink href="https://app.courier.com/settings/api-keys">A Courier API key</AppLink>
* <Doc href="/docs/in-app/authenticate-users">A backend endpoint that issues a Courier JWT</Doc>
* A message sent to that user on the `inbox` channel

## Set up

<Steps>
  <Step title="Install the SDK">
    Install the SDK for your framework.

    <CodeGroup>
      ```bash React theme={null}
      npm install @trycourier/courier-react
      # On React 17? Use @trycourier/courier-react-17 instead (identical API):
      # npm install @trycourier/courier-react-17
      ```

      ```bash Web Components theme={null}
      npm install @trycourier/courier-ui-inbox
      ```

      ```bash Vue theme={null}
      npm install @trycourier/courier-vue
      ```

      ```bash Angular theme={null}
      npm install @trycourier/courier-angular
      ```

      ```bash iOS theme={null}
      # Swift Package Manager: add https://github.com/trycourier/courier-ios
      # or CocoaPods:
      pod 'Courier_iOS'
      ```

      ```bash Android theme={null}
      # In settings.gradle add the JitPack repo, then the dependency in build.gradle:
      # maven { url 'https://jitpack.io' }
      # implementation 'com.github.trycourier:courier-android:<version>'
      ```

      ```bash Flutter theme={null}
      flutter pub add courier_flutter
      ```

      ```bash React Native theme={null}
      npm install @trycourier/courier-react-native
      # then, for iOS:
      cd ios && pod install
      ```
    </CodeGroup>

    On mobile, the SDK also needs native project setup before it renders: <Doc href="/docs/sdk-libraries/ios#installation">iOS</Doc>, <Doc href="/docs/sdk-libraries/android#installation">Android</Doc>, <Doc href="/docs/sdk-libraries/flutter#installation">Flutter</Doc>, and <Doc href="/docs/sdk-libraries/react-native#installation">React Native</Doc>.
  </Step>

  <Step title="Authenticate and render">
    Call `signIn` with a `userId` and a JWT from your backend, then mount the inbox component.

    <CodeGroup>
      ```jsx React highlight={8} theme={null}
      import { useEffect } from "react";
      import { useCourier, CourierInbox } from "@trycourier/courier-react";  // or "@trycourier/courier-react-17"

      export default function App() {
        const courier = useCourier();

        useEffect(() => {
          courier.shared.signIn({ userId, jwt });
        }, []);

        return <CourierInbox />;
      }
      ```

      ```html Web Components highlight={7} theme={null}
      <!-- Works with any framework, or none -->
      <courier-inbox id="inbox"></courier-inbox>

      <script type="module">
        import { Courier } from "@trycourier/courier-ui-inbox";

        Courier.shared.signIn({ userId, jwt });
      </script>
      ```

      ```vue Vue highlight={8} theme={null}
      <script setup lang="ts">
      import { onMounted } from "vue";
      import { CourierInbox, useCourier } from "@trycourier/courier-vue";

      const courier = useCourier();

      onMounted(() => {
        courier.shared.signIn({ userId, jwt });
      });
      </script>

      <template>
        <CourierInbox />
      </template>
      ```

      ```ts Angular highlight={14} theme={null}
      import { AfterViewInit, Component, inject } from "@angular/core";
      import { CourierInboxComponent, CourierService } from "@trycourier/courier-angular";

      @Component({
        selector: "app-root",
        standalone: true,
        imports: [CourierInboxComponent],
        template: `<courier-inbox></courier-inbox>`,
      })
      export class AppComponent implements AfterViewInit {
        private readonly courier = inject(CourierService);

        ngAfterViewInit(): void {
          this.courier.signIn({ userId, jwt });
        }
      }
      ```

      ```swift iOS highlight={4} theme={null}
      import Courier_iOS

      // Authenticate the user
      await Courier.shared.signIn(userId: userId, accessToken: jwt)

      // UIKit
      view.addSubview(CourierInbox())

      // SwiftUI
      CourierInboxView()
      ```

      ```kotlin Android highlight={1,3} theme={null}
      // Authenticate the user (signIn is a suspend function)
      lifecycleScope.launch {
        Courier.shared.signIn(userId = userId, accessToken = jwt)
      }

      // Render the inbox @Composable
      CourierInbox()
      ```

      ```dart Flutter highlight={5} theme={null}
      import 'package:courier_flutter/courier_flutter.dart';
      import 'package:courier_flutter/ui/inbox/courier_inbox.dart';

      // Authenticate the user
      await Courier.shared.signIn(accessToken: jwt, userId: userId);

      // Render the inbox widget
      CourierInbox();
      ```

      ```jsx React Native highlight={4} theme={null}
      import Courier, { CourierInboxView } from "@trycourier/courier-react-native";

      // Authenticate the user
      await Courier.shared.signIn({ accessToken: jwt, userId });

      // Render the inbox component
      return <CourierInboxView />;
      ```
    </CodeGroup>

    <Note>
      `CourierInbox` fills its container's width and takes its height from the parent, so size the parent or set a `height`. For a fixed-size widget, use the [popup menu](#render-as-a-popup-menu) below.
    </Note>
  </Step>
</Steps>

Full SDK references: <Doc href="/docs/sdk-libraries/courier-react-web">React</Doc>, <Doc href="/docs/in-app/web-components">Web Components</Doc>, <Doc href="/docs/sdk-libraries/courier-vue-web">Vue</Doc>, <Doc href="/docs/sdk-libraries/courier-angular-web">Angular</Doc>, <Doc href="/docs/sdk-libraries/ios">iOS</Doc>, <Doc href="/docs/sdk-libraries/android">Android</Doc>, <Doc href="/docs/sdk-libraries/flutter">Flutter</Doc>, <Doc href="/docs/sdk-libraries/react-native">React Native</Doc>.

### Render as a popup menu

On the web SDKs, the inbox can render as a popup menu instead of an inline list. The icon button opens the panel and carries an unread badge. Its default icon is an inbox glyph, themeable through `theme.popup.button.icon.svg`. Authentication is identical: keep the same `signIn` and swap the component. Popup menus are not available on mobile.

<Frame caption="The inbox as a popup menu: an icon button with an unread badge, and the panel it opens.">
  <img src="https://mintcdn.com/courier-4f1f25dc/9rcgucLA9fBnJt_U/assets/inbox-popup-menu.webp?fit=max&auto=format&n=9rcgucLA9fBnJt_U&q=85&s=f80d8917f34f11d1af0abdd9fa9ed966" alt="The Courier Inbox popup menu: an icon button with an unread badge above the open notification panel, which shows its own unread count in the header" className="mx-auto" width="1576" height="888" data-path="assets/inbox-popup-menu.webp" />
</Frame>

<CodeGroup>
  ```jsx React theme={null}
  import { CourierInboxPopupMenu } from "@trycourier/courier-react";  // or "@trycourier/courier-react-17"

  // Swap <CourierInbox /> for the popup menu
  return <CourierInboxPopupMenu />;
  ```

  ```html Web Components theme={null}
  <courier-inbox-popup-menu></courier-inbox-popup-menu>
  ```

  ```vue Vue theme={null}
  <script setup lang="ts">
  import { CourierInboxPopupMenu } from "@trycourier/courier-vue";
  </script>

  <template>
    <CourierInboxPopupMenu />
  </template>
  ```

  ```ts Angular theme={null}
  import { Component } from "@angular/core";
  import { CourierInboxPopupMenuComponent } from "@trycourier/courier-angular";

  @Component({
    selector: "app-root",
    standalone: true,
    imports: [CourierInboxPopupMenuComponent],
    template: `<courier-inbox-popup-menu></courier-inbox-popup-menu>`,
  })
  export class AppComponent {}
  ```
</CodeGroup>

<Note>
  Every platform, including mobile, exposes the message data and actions directly, so <Doc href="/docs/in-app/build-a-custom-inbox">Custom UI</Doc> shows how to build your own.
</Note>

#### Size and position the popup

<Frame caption="The popup menu at three different alignments, widths, and offsets.">
  <img src="https://mintcdn.com/courier-4f1f25dc/LdpdyPjJHKHJqFY9/assets/courier-inbox-popupmenu-position.webp?fit=max&auto=format&n=LdpdyPjJHKHJqFY9&q=85&s=9d731ae80029544028a91cd535ebf38c" alt="Three Courier Inbox popup menus side by side, each opening from a different corner of its trigger button and at a different width and height" className="mx-auto" width="1096" height="1266" data-path="assets/courier-inbox-popupmenu-position.webp" />
</Frame>

`popupAlignment` picks which corner the panel opens from, and the four CSS offsets nudge it from there. Set only the offsets that the alignment uses: a `top-*` alignment reads `top`, a `bottom-*` alignment reads `bottom`, and the horizontal half reads `left` or `right` to match.

| Prop                          | Default          | Description                                                                                 |
| ----------------------------- | ---------------- | ------------------------------------------------------------------------------------------- |
| `popupAlignment`              | `"top-left"`     | One of `top`, `center`, or `bottom`, paired with `left`, `center`, or `right`. Nine values. |
| `popupWidth`                  | `"440px"`        | Width of the panel. Any CSS length.                                                         |
| `popupHeight`                 | `"440px"`        | Height of the panel. Any CSS length.                                                        |
| `top` `right` `bottom` `left` | `40px` and `0px` | CSS offsets from the aligned corner, overriding the built-in pair.                          |

```jsx theme={null}
<CourierInboxPopupMenu
  popupAlignment="top-left"
  popupWidth="340px"
  popupHeight="400px"
  top="44px"
  left="44px"
/>
```

An unrecognized `popupAlignment` is ignored rather than rejected, and the panel keeps the alignment it had. Check the spelling against the nine values if a change appears to do nothing.

#### Give the inline inbox a height

`<CourierInbox />` defaults to `height="auto"` and grows with its messages. In a fixed-height container that means the page scrolls instead of the list, so set a height when the inbox sits inside a panel or sidebar.

```jsx theme={null}
<CourierInbox height="50vh" />
```

<Note>
  **Content Security Policy.**<br />
  The inbox calls Courier directly from the browser, so a site with a CSP has to allow these. `connect-src` needs `https://api.courier.com`, `https://inbox.courier.com`, `wss://realtime.courier.io`, and `wss://realtime.courier.com`. On the EU region, use `https://api.eu.courier.com`, `https://inbox.eu.courier.io`, and `wss://realtime.eu.courier.io`. The components style themselves inline, so `style-src` needs `'unsafe-inline'`.
</Note>

<Note>
  **Hosted preference center.**<br />
  If you embed the hosted preference center in an iframe, `frame-src` needs `https://view.notificationcenter.app`.
</Note>

## Verify

<Steps>
  <Step title="Sign in and open the inbox">
    Sign in a user and open the screen that renders the inbox.
  </Step>

  <Step title="Send a test message">
    <Doc href="/docs/in-app/send-to-the-inbox">Send a message</Doc> to that user on the `inbox` channel.
  </Step>

  <Step title="Confirm it appears">
    The message appears in the inbox in real time, and the unread count updates.
  </Step>
</Steps>

If the inbox stays empty, suspect authentication first. An expired or unscoped JWT signs in silently but returns no messages. See <Doc href="/docs/in-app/authenticate-users">Authenticate users</Doc> to check the token's `exp` and scopes.
