Using MailFlat with the Vercel AI SDK
mailflatToolSuite() spreads ready-made inbox tools into any Vercel AI SDK call, so the model can create an address, wait for the code and finish a signup on its own.
How it fits together
The suite is a dictionary of ready-made tools — you spread it into the tools option and the model does the rest. It speaks both the older parameters shape and the newer inputSchema one, so it works with the AI SDK version you already have rather than forcing an upgrade.
The tools
Named in camelCase here — createInbox, waitForOtp, deleteMessage — because they are TypeScript object keys you spread and destructure.
| Tool | Arguments | What it does |
|---|---|---|
| create_inbox | prefix? · label? · retention_hours? | Opens a real inbox and returns its address. |
| list_inboxes | — | Every inbox this API key can see. |
| read_messages | address | All messages in an inbox, newest first. |
| wait_for_otp | address · timeout? | Polls until a one-time code arrives, then returns it. |
| send_email | address · to · subject? · body? · html? | Sends a DKIM-signed email from the inbox. |
| delete_inbox | address | Deletes the inbox and every message in it. |
| delete_message | address · message_id | Deletes one message; the inbox itself stays. |
Install
The AI SDK itself is a peer dependency, so install it too if this is a fresh project.
npm i @mailflat/ai-sdk ai @ai-sdk/anthropic
One call, every tool
Everything after the model line is ordinary AI SDK code — the suite adds tools and nothing else.
import { mailflatToolSuite } from "@mailflat/ai-sdk";
import { anthropic } from "@ai-sdk/anthropic";
import { generateText } from "ai";
const result = await generateText({
model: anthropic("claude-opus-5"),
tools: {
...mailflatToolSuite({ apiKey: process.env.MAILFLAT_API_KEY }),
},
maxSteps: 10,
prompt:
"Open an inbox, sign up on staging.example.com with it, " +
"read the one-time code from the mail and submit it.",
});
console.log(result.text);
maxSteps is what lets the model call a tool, read the result and call another. AI SDK 5 renamed it — there you write stopWhen: stepCountIs(10) instead.
Only the tools this agent should have
It is a plain object, so pick from it like one. A support bot that reads mail has no business deleting inboxes.
const { createInbox, waitForOtp, readMessages } = mailflatToolSuite({
apiKey: process.env.MAILFLAT_API_KEY,
});
const result = await generateText({
model: anthropic("claude-opus-5"),
tools: { createInbox, waitForOtp, readMessages },
maxSteps: 8,
prompt: "Open an inbox and tell me the first code that arrives.",
});
Share one client with the rest of your app
Pass a configured @mailflat/sdk instance and both paths use the same base URL, retries and key. Also the seam a test fills with a fake.
import { MailFlat } from "@mailflat/sdk";
import { mailflatToolSuite } from "@mailflat/ai-sdk";
const mf = new MailFlat({
apiKey: process.env.MAILFLAT_API_KEY!,
baseUrl: "https://mail.example.com",
});
const tools = mailflatToolSuite({ client: mf });
// your own code and the model now share one client
const inbox = await mf.create({ label: "checkout", retentionHours: 2 });
Watch what it actually did
Tool results come back as data, so a run that looks fine can hide a timeout. Read the steps rather than trusting the final sentence.
for (const step of result.steps) {
for (const call of step.toolResults ?? []) {
console.log(call.toolName, call.result);
// e.g. waitForOtp { otp: null, error: "timeout" }
}
}
Worth knowing
Tool errors arrive as { error: "..." } instead of throwing, so the model can recover — but nothing surfaces them for you. Log tool results, or a timed-out waitForOtp reads as a successful run that simply found no code.
The key belongs on the server. Spreading this suite into a call that runs in the browser ships an account key to every visitor.
waitForOtp takes milliseconds and holds the step open while it polls; an inbox with end-to-end encryption returns { otp: null, encrypted: true } because the server cannot read it.
See also
MCP server
Run the MailFlat MCP server and any Model Context Protocol client gains real inbox tools: open an address, wait for the one-time code, send mail, clean up.
TypeScriptThe same package as JavaScript, with types shipped in the box: Message, CreateInboxOptions, WaitOptions and typed errors.
Waiting and timeoutsMail is asynchronous, so every email test waits for something. The difference between a solid suite and a flaky one is how that wait is written.