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

# Inbox tabs and feeds

> Filter the inbox into tabs by tag, read state, archive state, or date, and group them into feeds.

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

A tab is a filtered view of the same message stream. A feed groups tabs, so the inbox can offer several sets of them.

Start where your app already is: a tab is a saved query, not a separate mailbox. Nothing routes a message to a tab at send time. Every tab reads the same messages and shows the ones its filter matches, which is why one message can appear in several tabs at once.

<Frame caption="One feed offering five tabs, each a different filter over the same messages.">
  <img src="https://mintcdn.com/courier-4f1f25dc/LdpdyPjJHKHJqFY9/assets/courier-react-tabs.webp?fit=max&auto=format&n=LdpdyPjJHKHJqFY9&q=85&s=8a929734bd24c3b7962948dd2d21aec8" alt="The Courier Inbox header showing a row of tabs reading All, Unread, Read, Important, and Archived, with the All tab selected and carrying an unread count" className="mx-auto" width="1404" height="1244" data-path="assets/courier-react-tabs.webp" />
</Frame>

<CodeGroup>
  ```jsx React theme={null}
  import { CourierInbox, type CourierInboxFeed } from "@trycourier/courier-react";

  const feeds: CourierInboxFeed[] = [
    {
      feedId: "notifications",
      title: "Notifications",
      tabs: [
        { datasetId: "all", title: "All", filter: {} },
        { datasetId: "unread", title: "Unread", filter: { status: "unread" } },
        { datasetId: "important", title: "Important", filter: { tags: ["important"] } },
        { datasetId: "archived", title: "Archived", filter: { archived: true } },
      ],
    },
  ];

  export default function App() {
    return <CourierInbox feeds={feeds} />;
  }
  ```

  ```js Web Components theme={null}
  const inbox = document.getElementById("inbox");

  inbox.setFeeds([
    {
      feedId: "notifications",
      title: "Notifications",
      tabs: [
        { datasetId: "all", title: "All", filter: {} },
        { datasetId: "unread", title: "Unread", filter: { status: "unread" } },
        { datasetId: "important", title: "Important", filter: { tags: ["important"] } },
        { datasetId: "archived", title: "Archived", filter: { archived: true } },
      ],
    },
  ]);
  ```

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

  const feeds: CourierInboxFeed[] = [
    {
      feedId: "notifications",
      title: "Notifications",
      tabs: [
        { datasetId: "all", title: "All", filter: {} },
        { datasetId: "unread", title: "Unread", filter: { status: "unread" } },
        { datasetId: "important", title: "Important", filter: { tags: ["important"] } },
        { datasetId: "archived", title: "Archived", filter: { archived: true } },
      ],
    },
  ];
  </script>

  <template>
    <CourierInbox :feeds="feeds" />
  </template>
  ```

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

  @Component({
    selector: "app-root",
    standalone: true,
    imports: [CourierInboxComponent],
    template: `<courier-inbox [feeds]="feeds"></courier-inbox>`,
  })
  export class AppComponent {
    feeds: CourierInboxFeed[] = [
      {
        feedId: "notifications",
        title: "Notifications",
        tabs: [
          { datasetId: "all", title: "All", filter: {} },
          { datasetId: "unread", title: "Unread", filter: { status: "unread" } },
          { datasetId: "important", title: "Important", filter: { tags: ["important"] } },
          { datasetId: "archived", title: "Archived", filter: { archived: true } },
        ],
      },
    ];
  }
  ```
</CodeGroup>

**Tabs and feeds are web only.** React, Vue, Angular, and the Web Components take them. The iOS, Android, Flutter, and React Native inboxes render a single list, so build the equivalent switch in your own UI with <Doc href="/docs/in-app/build-a-custom-inbox#data-and-actions">the data and actions</Doc>.

## What each field does

A feed is a container. It creates nothing on its own, and only its tabs load messages.

| Feed field | Type                | Description                                                                |
| ---------- | ------------------- | -------------------------------------------------------------------------- |
| `feedId`   | string              | Identifies the feed, and selects it programmatically. Unique across feeds. |
| `title`    | string              | The name shown in the inbox header.                                        |
| `iconSVG`  | string              | Optional icon markup for the feed button.                                  |
| `tabs`     | `CourierInboxTab[]` | The tabs this feed offers.                                                 |

| Tab field   | Type                        | Description                                                                           |
| ----------- | --------------------------- | ------------------------------------------------------------------------------------- |
| `datasetId` | string                      | Identifies the tab's dataset in the datastore. Unique across every tab in every feed. |
| `title`     | string                      | The name shown on the tab.                                                            |
| `filter`    | `CourierInboxDatasetFilter` | Which messages the tab shows. Required, and `{}` means every unarchived message.      |

**`datasetId` has to be unique across all feeds, not just within one.** Tabs are flattened into one set of datasets when the inbox registers them. Two tabs sharing an ID become a single dataset that both render, so you get identical messages under two names and no error explaining it. Slugify the title and prefix it with the feed's, as in `jobs-unread`.

## Filter a tab

Set fields are AND'd together. Several `tags` match as OR, so a message carrying any one of them is included.

| Filter field | Type                   | Description                                                                                |
| ------------ | ---------------------- | ------------------------------------------------------------------------------------------ |
| `tags`       | `string[]`             | Messages carrying any of these tags.                                                       |
| `archived`   | boolean                | Include archived messages. Defaults to `false`.                                            |
| `status`     | `'read'` or `'unread'` | Limit to one read state. Unset applies neither.                                            |
| `from`       | string                 | ISO 8601 datetime. Only messages created at or after it, as in `new Date().toISOString()`. |

Tags come from the send side. <Doc href="/docs/in-app/send-to-the-inbox#tag-a-message-for-a-tab">Tag a message for a tab</Doc> covers putting them on a message, and a tag has to be on the message before it arrives, because a filter cannot add one later.

`from` is how you keep an existing user's backlog out of a newly added tab. Pin it to the moment the tab shipped and the tab starts empty rather than full of history.

## Several feeds

Give the inbox more than one feed and the header gains a picker. Each feed brings its own tabs.

<Frame caption="Four feeds in the header picker, each with its own set of tabs.">
  <img src="https://mintcdn.com/courier-4f1f25dc/LdpdyPjJHKHJqFY9/assets/courier-react-feeds.webp?fit=max&auto=format&n=LdpdyPjJHKHJqFY9&q=85&s=8572aafd2ff6ef8c5410a838acb11479" alt="The Courier Inbox feed picker open, listing All, Jobs, My Posts, and Mentions as selectable feeds" className="mx-auto" width="1376" height="1218" data-path="assets/courier-react-feeds.webp" />
</Frame>

```jsx theme={null}
const feeds: CourierInboxFeed[] = [
  { feedId: "all", title: "All", tabs: [{ datasetId: "all", title: "All", filter: {} }] },
  { feedId: "jobs", title: "Jobs", tabs: [{ datasetId: "jobs-all", title: "All", filter: { tags: ["job"] } }] },
  {
    feedId: "my-posts",
    title: "My Posts",
    tabs: [
      { datasetId: "posts-all", title: "All", filter: {} },
      { datasetId: "posts-comments", title: "Comments", filter: { tags: ["comment"] } },
      { datasetId: "posts-reactions", title: "Reactions", filter: { tags: ["reaction"] } },
    ],
  },
];
```

The chrome adapts to what you pass. One feed hides the feed picker. A feed holding one tab hides the tab bar and moves that tab's unread count next to the feed name. So a single unfiltered feed and tab renders as a plain list, which is what an inbox with no `feeds` at all shows.

## Select a feed or tab in code

The inbox element exposes `selectFeed(feedId)` and `selectTab(datasetId)`, and reports changes back through `onFeedChange` and `onTabChange`. Both take the ids you defined above.

```js theme={null}
const inbox = document.getElementById("inbox");

inbox.selectFeed("jobs");
inbox.selectTab("jobs-all");
```

On React, Vue, and Angular these are methods on the underlying element rather than props, so reach them through a ref. To change what the tabs contain instead of which one is showing, pass a new `feeds` array and the inbox re-registers.

## Unread counts

Each tab carries its own unread count, and `totalUnreadCount` on the datastore counts every message rather than the selected tab's. A tab filtered to `status: 'unread'` empties as the reader works through it, because a message that leaves the filter leaves the tab.
