> ## 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` from the Node SDK (`@trycourier/courier` v7 and later, where the client is the default import). 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.

# Message and journey logs

> Every message and journey run in one place: status, timeline, content sent, and why it failed.

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

Logs show what happened to every message: who got it, which provider carried it, what it said, and whether it was delivered, opened, or clicked. Journey logs do the same for each step of a run.

Search them in the Courier dashboard, no code needed, or fetch the same records [over the API or MCP](#read-logs-over-the-api-or-mcp).

| You want to know                              | Look at                                                            |
| :-------------------------------------------- | :----------------------------------------------------------------- |
| Whether a message was delivered               | The message's status in [Message logs](#message-logs)              |
| Why a message failed                          | The **Error Encountered** event in [its timeline](#read-a-message) |
| Exactly what the recipient received           | [The rendered content](#see-the-rendered-content)                  |
| What a journey did for one user               | [Journey logs](#journey-logs)                                      |
| Why a journey took the wrong branch           | [The step context](#read-a-run) on the branch node                 |
| Any of the above in your own code or AI agent | [The API or MCP](#read-logs-over-the-api-or-mcp)                   |

## Message logs

<AppLink href="https://app.courier.com/logs">Logs</AppLink> lists every message in the current environment, newest first. Each row shows the message's status, template, recipient, and the channels it went out on.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/logs-list.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=ddad3817aaccdb992d3bd84d028c2379" alt="The message logs list" width="3010" height="1636" data-path="assets/monitor/logs-list.png" />
</Frame>

### Find a message or a recipient

The histogram at the top shows message volume per day, colored by status. Drag across it to filter the list to that date range.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/logs-histogram.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=86a0423398065166fd2b8dbdd199be54" alt="The logs histogram, with daily message volume colored by status" width="2678" height="896" data-path="assets/monitor/logs-histogram.png" />
</Frame>

Filter the list by any combination of:

| Filter           | Matches                                                                              |
| :--------------- | :----------------------------------------------------------------------------------- |
| **Date**         | A date range inside your log retention window                                        |
| **Recipient**    | An email address or a user ID                                                        |
| **Notification** | One template                                                                         |
| **Status**       | One or more of Queued, Sent, Delivered, Opened, Clicked, Undeliverable, and Unmapped |
| **Errors**       | Messages with an error anywhere in their timeline                                    |
| **Provider**     | One or more of your connected providers                                              |

The `requestId` the Send API returns is the message ID. It's the same ID the <Endpoint method="GET" path="/messages/{message_id}" name="Get message" href="/docs/api-reference/messages/get-message" /> endpoint and <Doc href="/docs/monitor/webhooks/outbound">outbound webhooks</Doc> use.

### Read a message

Select a message to open its detail view:

* **Summary**: the message ID, the recipient ID, and a timestamp for each stage, such as enqueued, sent, and first delivery.
* **Timeline**: every event in the message's life, in order. Select an event to see its details, including the provider's raw response.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/logs-detail.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=e54d1cbf46d24509fbaa3ce7f5be7a21" alt="A message's detail view, with the summary above the timeline of events" width="2232" height="1476" data-path="assets/monitor/logs-detail.png" />
</Frame>

When a message fails, the timeline has an **Error Encountered** event. Select it to read the error.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/logs-error.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=ba44745a5edffdd349def820cfedcb42" alt="The Error Encountered event open in the timeline, showing the error message" width="2232" height="1468" data-path="assets/monitor/logs-error.png" />
</Frame>

A message's status only moves forward. `DELIVERED` comes from your provider, and `OPENED` and `CLICKED` come from Courier's own tracking, so a message can show an open before its delivery is confirmed. <Doc href="/docs/send/statuses">Message statuses and errors</Doc> lists every status, and <Doc href="/docs/monitor/tracking">Tracking</Doc> explains which channels report opens and clicks.

### See the rendered content

Teammates with Full log access see the rendered message in the dashboard. To get it in code, call <Endpoint method="GET" path="/messages/{message_id}/output" name="Get message content" href="/docs/api-reference/messages/get-message-content" />. It returns what the recipient received, per channel: the subject, HTML, and text body for email, and the message text for SMS and push.

```bash theme={null}
curl https://api.courier.com/messages/1-612fa552-15f7d6ba51bf229857c037a7/output \
  -H "Authorization: Bearer $COURIER_API_KEY"
```

## Journey logs

Every time a journey is invoked, Courier records a run: the full trace of that journey for that user, identified by the `runId` the invoke call returns.

Open a journey and select its **Logs** tab to see its runs. To see runs across every journey, open the Journeys logs page, which adds a template ID column. Over the API, <Endpoint method="GET" path="/journeys/runs" name="List Journey runs" href="/docs/api-reference/journeys/list-journey-runs" /> lists runs and <Endpoint method="GET" path="/journeys/runs/{run_id}/steps" name="List steps for a Journey run" href="/docs/api-reference/journeys/list-steps-for-a-journey-run" /> returns each step a run took.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/journey-run-list.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=850ae3e7bba54212122749ab566ac71b" alt="A journey's Logs tab listing its runs" width="3452" height="1922" data-path="assets/monitor/journey-run-list.png" />
</Frame>

### Find a run

Search by run ID or template ID, or filter by status, date range, or recipient. A run has one of these statuses:

| Status         | Meaning                                                                                                   |
| :------------- | :-------------------------------------------------------------------------------------------------------- |
| **Processing** | The run is executing.                                                                                     |
| **Waiting**    | The run is paused on a delay or throttle node.                                                            |
| **Processed**  | Every node finished.                                                                                      |
| **Error**      | A node failed.                                                                                            |
| **Canceled**   | The run was stopped before it finished. See <Doc href="/docs/journeys/invoke#cancel-a-run">Cancel a run</Doc>. |

### Read a run

Select a run to see it on the journey canvas, with each node marked by what happened to it. The view shows which nodes ran and in what order, which path each branch took, where a waiting run is paused, and which node failed.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/journey-run-detail.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=c1dc11889e6769b0da6862fc61d8c524" alt="A run shown on the journey canvas, with the outcome marked on each node" width="3452" height="1922" data-path="assets/monitor/journey-run-detail.png" />
</Frame>

Select a node to see its step context:

| Field                    | What it shows                                                              |
| :----------------------- | :------------------------------------------------------------------------- |
| **Input data**           | The data the node received                                                 |
| **Profile data**         | The user's profile at that step                                            |
| **Conditions evaluated** | Each condition on a branch or conditional node, and whether it passed      |
| **Output**               | The response for a fetch data node, or the delivery status for a send node |

Step context answers most "why" questions. If a branch took the wrong path, the conditions show the value it compared, such as `data.plan` being `free` instead of `pro`. If a send failed, the profile shows the missing field, such as an empty `email`.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/66t_J9wvizdshWJp/assets/monitor/journey-step-context.png?fit=max&auto=format&n=66t_J9wvizdshWJp&q=85&s=72cc91e5861dff8fd0922021ab6f6910" alt="The step context for one node, showing its input data, profile, and output" width="3456" height="1498" data-path="assets/monitor/journey-step-context.png" />
</Frame>

A run always shows the journey version it started on, even after you publish changes, so you see what that user went through.

## Read logs over the API or MCP

Everything in the Logs page is available over the API, for support tools, scripts, and AI agents:

| To get                                                              | Call                                                                                                                                                           |
| :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Messages, filtered by recipient, template, status, provider, or tag | <Endpoint method="GET" path="/messages" name="List messages" href="/docs/api-reference/messages/list-messages" />                                                   |
| One message's status and delivery timestamps                        | <Endpoint method="GET" path="/messages/{message_id}" name="Get message" href="/docs/api-reference/messages/get-message" />                                          |
| Every status change, in order                                       | <Endpoint method="GET" path="/messages/{message_id}/history" name="Get message history" href="/docs/api-reference/messages/get-message-history" />                  |
| The content the recipient received                                  | <Endpoint method="GET" path="/messages/{message_id}/output" name="Get message content" href="/docs/api-reference/messages/get-message-content" />                   |
| Journey runs                                                        | <Endpoint method="GET" path="/journeys/runs" name="List Journey runs" href="/docs/api-reference/journeys/list-journey-runs" />                                      |
| Each step a journey run took                                        | <Endpoint method="GET" path="/journeys/runs/{run_id}/steps" name="List steps for a Journey run" href="/docs/api-reference/journeys/list-steps-for-a-journey-run" /> |

With the <Doc href="/docs/resources/mcp">Courier MCP server</Doc> connected, your AI agent can look up messages for you. Ask it something like "why didn't Sarah Bennett get her receipt?" and it uses the `list_messages`, `get_message`, `get_message_history`, and `get_message_content` tools to find the message and read its timeline.

## Other logs

* **Broadcasts**: each recipient gets their own entry in message logs. The per-recipient view for one broadcast is in <Doc href="/docs/monitor/analytics#broadcasts">Analytics</Doc>.
* **Preference changes**: open a user in <AppLink href="https://app.courier.com/users">Users</AppLink> and select their preference log to see when they opted in or out of each topic. The <Endpoint method="GET" path="/users/{user_id}/preferences" name="Get user's Preferences" href="/docs/api-reference/user-preferences/get-users-preferences" /> endpoint returns current preferences only, not their history.
* **Automations**: runs for the previous workflow product are covered in <Doc href="/docs/journeys/automations/debugger">Automations (Legacy)</Doc>.
* **Workspace changes**: who changed which setting, and when, is in the <Doc href="/docs/workspaces/audit-trail">audit trail</Doc>.

## Troubleshoot a message

If a message shows `SENT` and the recipient doesn't have it:

1. Open the `SENT` event in the timeline and read the provider's response.
2. Look the message up in your provider's own activity log, such as SendGrid Activity or the SES console, for a bounce, block, or deferral.
3. Check SPF, DKIM, and DMARC on your sending domain.

If a message shows `DELIVERED` and the recipient still doesn't have it, the recipient's mail server accepted it. A spam filter or security gateway on their side is the usual cause.

<Doc href="/docs/guides/debug-delivery">Debug email delivery</Doc> walks through each case.

## Limits & behavior

* **`SENT` is not a failure.** Courier handed the message to the provider and hasn't heard back, which usually means delivery tracking isn't set up for that provider. <Doc href="/docs/monitor/analytics#set-up-delivery-tracking">Set up delivery tracking</Doc> to get `DELIVERED`.
* **Logs are per environment.** Test messages appear only in Test logs.
* **Retention depends on your plan.** Developer and Business keep 30 days of message history. Enterprise keeps 365 days. Searches and reads are clipped to that window, so export anything you need to keep longer. A message's own event history is capped at roughly 93 days on every plan, so an older message still lists but returns no timeline.

## FAQ

<AccordionGroup>
  <Accordion title="How do I check whether a notification was delivered?">
    Search Courier's message logs for the recipient's email address or user ID and open the message. `DELIVERED` means the provider confirmed it, and `SENT` means Courier is still waiting to hear back.
  </Accordion>

  <Accordion title="Can I see the exact email a user received?">
    The dashboard shows the rendered message to teammates with Full log access. Over the API, <Endpoint method="GET" path="/messages/{message_id}/output" name="Get message content" href="/docs/api-reference/messages/get-message-content" /> returns the content per channel, including the subject and HTML for email.
  </Accordion>

  <Accordion title="How long does Courier keep message logs?">
    Developer and Business keep 30 days of message history, and Enterprise keeps 365 days. A message's own event history is capped at roughly 93 days on every plan.
  </Accordion>

  <Accordion title="Do I need to be a developer to use Courier logs?">
    Logs are in the Courier dashboard, and anyone on the workspace can search them by recipient, template, or status without writing code. On Enterprise, roles set how much detail they see: Level 1 shows a summary, Level 2 adds status detail and the request data, and Full also shows the rendered message. See <Doc href="/docs/workspaces/team-access">Team access, roles, and SSO</Doc>.
  </Accordion>

  <Accordion title="How do I match a log entry to my API call?">
    The `requestId` in your Send API response is the message ID. Fetch the message with it through the Messages API, or match it to `message:updated` webhook events.
  </Accordion>

  <Accordion title="Why does a message show OPENED before DELIVERED?">
    Courier records opens and clicks the moment they happen. `DELIVERED` waits for the provider's confirmation, which can take minutes or never arrive.
  </Accordion>

  <Accordion title="How do I find a journey run from my code?">
    The invoke call returns a `runId`. Search for it in the journey's Logs tab.
  </Accordion>

  <Accordion title="Why does an old journey run look different from my current journey?">
    A run shows the version that was published when it started, not your latest draft.
  </Accordion>
</AccordionGroup>
