> ## 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.
> To translate a template, write one locale at a time with `PUT /notifications/{id}/locales/{localeId}`, which merges into the existing translations. `PUT /notifications/{id}/content` and `PUT /notifications/{id}/elements/{elementId}` overwrite translations along with the content, so send each element's `id` and `locales` back, and first remove every `checksum` and any `locales` key that starts with an underscore.
> 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 from the Vercel AI SDK

> Let an AI agent built on the Vercel AI SDK notify customers through Courier, with a tool() and a Zod schema or the Courier MCP server.

Your AI agent decides who to tell and when, and Courier handles channels, routing, failover, preferences, and delivery logs.

Give the agent Courier in one of two ways:

* **A Courier tool** gives you tight control. You write one function that sends one published template, and the agent fills in only the fields your schema allows.
* **The Courier MCP server** gives the agent the full API: 170+ tools for sending, users, templates, and logs, with no tool code to write.

> [!TIP]
>
> Build with a **Test** environment key. Test's built-in email provider only delivers to the address you signed up with, so until you connect your own provider, an agent can't reach a real customer by mistake.

## Prerequisites

* [A Courier Test API key](https://app.courier.com/~/test/platform/api-keys)
* [A published template
  DOC](https://www.courier.com/docs/design/templates/overview.md)
* [A user profile with contact info, or the inbox channel
  GUIDE](https://www.courier.com/docs/guides/import-your-users.md)
* [A Vercel AI Gateway API key](https://vercel.com/ai-gateway)
* Node.js 22 or later

## Set up the project

In a new folder, install the AI SDK, its MCP client, and the Courier SDK:

```bash theme={null}
npm init -y && npm pkg set type=module
npm install ai @ai-sdk/mcp @trycourier/courier zod
npm install --save-dev tsx
```

Set both keys in the shell that runs the agent:

```bash theme={null}
export AI_GATEWAY_API_KEY="YOUR_AI_GATEWAY_API_KEY"
export COURIER_API_KEY="YOUR_COURIER_API_KEY"
```

The model strings below route through Vercel AI Gateway, the AI SDK's default provider, which reads `AI_GATEWAY_API_KEY`. Any AI SDK provider works in its place.

## Give the agent a Courier tool

**1. Write the agent**

The tool sends one template and takes three inputs. The AI SDK validates each call against `inputSchema` before `execute` runs, so the agent can't send a status other than `in_progress` or `resolved`.

```typescript agent.ts lines theme={null}
import { randomUUID } from "node:crypto";
import Courier from "@trycourier/courier";
import { generateText, isStepCount, tool } from "ai";
import { z } from "zod";

// Reads COURIER_API_KEY from the environment.
const courier = new Courier();

const sendTicketUpdate = tool({
  description: "Tell a customer that their support ticket changed status.",
  inputSchema: z.object({
    user_id: z.string().describe("The customer's Courier user ID"),
    ticket_id: z.string().describe("The support ticket ID"),
    status: z.enum(["in_progress", "resolved"]),
  }),
  execute: async ({ user_id, ticket_id, status }) => {
    const { requestId } = await courier.send.message({
      message: {
        to: { user_id },
        template: "nt_01kx4h2jdafq8bk9aftxak4b40",
        data: { ticket_id, status },
      },
      "Idempotency-Key": randomUUID(),
    });
    return { requestId };
  },
});

const { text } = await generateText({
  model: "anthropic/claude-sonnet-5.5",
  system: "You are a support agent. Tell customers when their ticket changes status, then reply with the requestId.",
  tools: { send_ticket_update: sendTicketUpdate },
  stopWhen: isStepCount(5),
  prompt: "Ticket T-4521 for user_123 is resolved. Let the customer know.",
});

console.log(text);
```

The key in `tools` is the name the model calls. `stopWhen` lets the model call the tool and then write its reply. Without it, `generateText` stops after the first step.

The Courier SDK retries failed requests, and the `Idempotency-Key` makes those retries safe: a retried request returns the first response instead of sending twice. Use a fresh key for each send. A key built from the ticket and status would replay a failed first attempt, and silence a second notice with the same status, for 25 hours.

**2. Run it**

```bash theme={null}
npx tsx agent.ts
```

The agent calls `send_ticket_update` and replies with the `requestId`. The template holds the content, so the agent picks the recipient and the moment without writing the message.

## Connect the Courier MCP server

**1. Point the agent at the server**

Create an MCP client with the HTTP transport and your key in the `api_key` header, then hand its tools to `generateText`.

```typescript agent.ts lines theme={null}
import { createMCPClient } from "@ai-sdk/mcp";
import { generateText, isStepCount } from "ai";

const mcpClient = await createMCPClient({
  transport: {
    type: "http",
    url: "https://mcp.courier.com",
    headers: { api_key: process.env.COURIER_API_KEY! },
  },
});

try {
  // Only the tools this agent needs: send a template, trace a message.
  const { send_message_template, trace_message } = await mcpClient.tools();

  const { text } = await generateText({
    model: "anthropic/claude-sonnet-5.5",
    system:
      "You are a support agent. To notify a customer, send the published Courier template nt_01kx4h2jdafq8bk9aftxak4b40 with ticket_id and status in data, then reply with the requestId.",
    tools: { send_message_template, trace_message },
    stopWhen: isStepCount(5),
    prompt: "Ticket T-4521 for user_123 is resolved. Let the customer know.",
  });
  console.log(text);
} finally {
  await mcpClient.close();
}
```

`mcpClient.tools()` returns every Courier tool, keyed by name. Hand the agent only the ones it needs, here sending a template and tracing a message, so a support agent can't reach write tools like profile deletes or broadcasts. Close the client when the run ends.

The MCP send tools take a `user_id` or a `list_id`, not an email address, so the customer needs a saved profile.

**2. Run it**

```bash theme={null}
npx tsx agent.ts
```

The agent calls `send_message_template` and replies with the `requestId`.

## Ask a person for approval

Send a template with an action button to Slack, Microsoft Teams, or the Inbox, and point the button at a URL in your app. The click reaches your app, not Courier, so record the decision there and resume the agent. The [action element
DOC](https://www.courier.com/docs/design/elemental/elements/action.md), [Slack
DOC](https://www.courier.com/docs/integrations/direct-message/slack.md), [Microsoft Teams
DOC](https://www.courier.com/docs/integrations/direct-message/microsoft-teams.md), and [Inbox
DOC](https://www.courier.com/docs/in-app/customize-the-inbox.md) pages cover each channel.

## Verify

**1. Find the request in Logs**

Copy the `requestId` from the agent's reply, open [Logs](https://app.courier.com/logs), and find that request. Logs are per environment, so a Test send appears only in Test logs.

**2. Confirm it delivered**

Expand the message timeline and confirm the provider accepted it. A channel with no configured provider shows as `UNROUTABLE`, so start with the inbox channel, which needs no setup.

## Next steps

* [Send from an AI agent
  GUIDE](https://www.courier.com/docs/guides/send-from-an-ai-agent.md) to test sends from Claude Code, Cursor, or Codex
* [The Courier MCP server
  DOC](https://www.courier.com/docs/resources/mcp.md) for every tool the server exposes
* [Send a message](https://www.courier.com/docs/api-reference/send/send-a-message.md) for every field a send accepts
* [Preferences
  DOC](https://www.courier.com/docs/recipients/preferences/overview.md), so a template mapped to a subscription topic respects each customer's opt-outs
