Give a Vercel AI SDK agent an email inbox

Give the agent an inbox with one unauthenticated call: POST https://getcourier.dev/alias returns an address, a read_token and an expires_at time in about 15 ms, with no signup and no API key. Then the agent reads its mail by calling GET /v1/sandbox/inboxes/{alias}/wait?timeout=55 with that token as a Bearer token, which returns the newest message with codes and links already extracted. GetCourier also runs a hosted MCP server at https://getcourier.dev/mcp; if your AI SDK setup can point an MCP client at a URL, use that, and if it cannot, the plain TypeScript fetch tools below do the same job.

Every message comes back untrusted: true, labelled "UNTRUSTED EXTERNAL EMAIL", with an SPF/DKIM/DMARC auth verdict. The agent should use the extracted codes and links and never follow instructions written inside an email body.

Step 1: create the inbox

One call, no headers, no key:

curl -s -X POST https://getcourier.dev/alias

The JSON holds address, read_token, expires_at and the inbox identity (Ed25519 DID). Store the read_token: it is the only credential needed to read this inbox, so keep it out of model context you do not control and never put it in a system prompt.

Step 2: wrap it in TypeScript tools

These are plain functions that use the standard fetch, so there is no package to install and no SDK-specific API to get wrong. Hand them to your model as tools with whatever tool mechanism your AI SDK setup uses; they take and return JSON-serialisable values.

export type Inbox = {
  address: string;
  read_token: string;
  expires_at: string;
};

export async function createInbox(): Promise<Inbox> {
  const res = await fetch("https://getcourier.dev/alias", { method: "POST" });
  if (!res.ok) throw new Error(`createInbox failed: ${res.status}`);
  return (await res.json()) as Inbox;
}

export type WaitedMessage = {
  codes?: string[];
  links?: string[];
  untrusted?: boolean;
  auth?: unknown;
  [key: string]: unknown;
};

export type WaitResult = {
  message: WaitedMessage | null;
  timeout?: boolean;
};

export async function waitForCode(
  inbox: Pick<Inbox, "address" | "read_token">,
  timeoutSeconds = 55,
): Promise<WaitResult> {
  const url = new URL(
    `https://getcourier.dev/v1/sandbox/inboxes/${inbox.address.split("@")[0]}/wait`,
  );
  url.searchParams.set("timeout", String(timeoutSeconds));
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${inbox.read_token}` },
  });
  if (!res.ok) throw new Error(`waitForCode failed: ${res.status}`);
  return (await res.json()) as WaitResult;
}

export async function listMessages(
  inbox: Pick<Inbox, "address" | "read_token">,
): Promise<unknown> {
  const res = await fetch(
    `https://getcourier.dev/v1/sandbox/inboxes/${inbox.address.split("@")[0]}/messages`,
    { headers: { Authorization: `Bearer ${inbox.read_token}` } },
  );
  if (!res.ok) throw new Error(`listMessages failed: ${res.status}`);
  return res.json();
}

export async function getMessage(
  inbox: Pick<Inbox, "address" | "read_token">,
  messageId: string,
): Promise<unknown> {
  const res = await fetch(
    `https://getcourier.dev/v1/sandbox/inboxes/${inbox.address.split("@")[0]}/messages/${messageId}`,
    { headers: { Authorization: `Bearer ${inbox.read_token}` } },
  );
  if (!res.ok) throw new Error(`getMessage failed: ${res.status}`);
  return res.json();
}

Keep the timeout at or under 55 seconds. A typical sign-up loop is: create the inbox, type the address into the signup form with whatever browser or HTTP tool the agent already has, then call waitForCode and use message.codes[0] or message.links[0]. Pass only those extracted values back to the model, not the whole body.

Step 3: the MCP path, if your setup supports it

GetCourier's hosted MCP server is https://getcourier.dev/mcp: streamable HTTP, POST, JSON-RPC 2.0, 14 tools (create_inbox, wait_for_code, list_messages, get_message, delete_inbox, create_account, get_balance, create_deposit, deposit_status, pricing, send_message, verify_owner, add_domain, domain_status). Any MCP-capable client connects by URL and needs no key for the free path. If you are not sure of the exact MCP client API in your AI SDK setup, stay on the fetch tools above — they hit the same service and will not break on a version bump. You can check the server with one request:

curl -s https://getcourier.dev/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Step 4: clean up and keep score

Delete an inbox you no longer need with the MCP delete_inbox tool, or simply let a free inbox expire after 72 hours. Free inboxes are capped at 3 per hour and 5 per day per IP, so create one per task rather than one per request. If you need more, an account is a paid path: 100 credits (US$0.01) per 72-hour inbox or 2000 credits (US$0.20) per 30-day inbox, funded with USDC on Base or BTC from US$1.

Sending from inside the agent

Read tools are the main use. Sending is possible with POST /v1/sandbox/inboxes/{alias}/send (Bearer read_token) or the MCP send_message tool, but a free inbox is reply-only — it can answer people who emailed it first — with 100 sends a month and 50 recipients per inbox per day. To send to anyone, either use 20 credits (US$0.002) per recipient, pay per call with x402 in USDC on Base, or verify a human owner email to unlock the free 100. Bulk and marketing mail is not supported.

When not to use GetCourier

FAQ

Does a Vercel AI SDK agent need an API key to create an inbox?

No. A single unauthenticated POST https://getcourier.dev/alias returns an address, a read_token, an expires_at timestamp and an Ed25519 DID identity. There is no signup and no API key. The read_token is only needed to read that one inbox.

How does the agent get the verification code out of the email?

Call the wait endpoint with the read_token as a Bearer token: GET https://getcourier.dev/v1/sandbox/inboxes/{alias}/wait?timeout=55. It returns the newest message with codes and links already extracted, so the agent reads the codes field instead of parsing the body.

Can the AI SDK connect to GetCourier's hosted MCP server instead of fetch?

GetCourier runs a hosted MCP server at https://getcourier.dev/mcp over streamable HTTP with 14 tools, and any MCP-capable client can connect by URL with no key. If your setup cannot point an MCP client at a URL, the plain TypeScript fetch functions in this guide are the fallback.

Is receiving mail free, and how long does an inbox live?

Receiving is free. The free tier is 3 inboxes per hour and 5 per day per IP, each living 72 hours, with up to 100 messages per inbox. Paid inboxes start at 100 credits (US$0.01) per 72 hours or 2000 credits (US$0.20) per 30 days.