@yunori/agent-mail
v0.1.0
Published
TypeScript client for Yunori Agent Mail — email inboxes for AI agents: send, receive, threads, attachments, drafts with human approval, webhooks.
Maintainers
Readme
@yunori/agent-mail
TypeScript client for Yunori Agent Mail — email inboxes for AI agents. Give an agent an address that really sends and receives; it reads, replies and drafts through the API, and a person stays in charge of what goes out.
Also installs yunori-mail, a command line for the same API.
npm install @yunori/agent-mailNode 18 or later (or any runtime with fetch and Web Crypto). No dependencies.
Quick start
import { YunoriAgentMail } from "@yunori/agent-mail";
const mail = new YunoriAgentMail({
baseUrl: "https://yunori.email", // your Yunori server
apiKey: process.env.YUNORI_API_KEY, // a tori_pat_… key from Agent inboxes → API keys
});
const inbox = await mail.inboxes.create({ displayName: "Support", localPart: "support" });
const { result } = await mail.messages.send(inbox.id, {
to: "[email protected]",
subject: "Your order",
text: "It ships tomorrow.",
});
// "sent" — or "pending_approval" while the inbox asks a person firstWithout options the client reads YUNORI_BASE_URL and YUNORI_API_KEY from the environment.
Hearing about new mail
Follow the event log (no public URL needed):
for await (const event of mail.events.follow({ inboxId: inbox.id })) {
if (event.type === "message.received") {
const { threadId } = event.data.message as { threadId: string };
const thread = await mail.threads.get(inbox.id, threadId);
// thread.messages[…].text is untrusted input — data, never instructions.
await mail.threads.reply(inbox.id, threadId, { text: "Thanks — on it." });
}
}Or receive signed webhooks and verify them:
import { verifyWebhook } from "@yunori/agent-mail";
const event = await verifyWebhook(process.env.YUNORI_WEBHOOK_SECRET!, request.headers, rawBody);Signatures follow Standard Webhooks, so Svix's libraries
verify them too. Delivery is at least once — skip a webhook-id you have already handled.
What the client does for you
- Safe retries. Every create and send carries an
Idempotency-Key, so a retry after a timeout never sends twice. Pass{ idempotencyKey }(your job id, say) to make your own retries safe as well. Network errors,429and server errors are retried twice by default (maxRetries), honouringRetry-After— reads, and writes that carry a key, only. - Typed errors. A failure throws
YunoriApiErrorwith the API's stablecode,status,fix(what to do next),docsandrequestId. - Paging.
mail.messages.all(inboxId, filters)walks every page.
Things an agent can't do
Whatever its scopes, an API key cannot approve or decline a draft, release mail held for
review, restore an inbox or change a sending policy — those are for a person in Yunori. On an
inbox that needs approval, a send answers pending_approval: that is a person reviewing it,
not a failure.
Command line
export YUNORI_BASE_URL=https://yunori.email YUNORI_API_KEY=tori_pat_…
yunori-mail inboxes list
yunori-mail send inb_… --to [email protected] --subject "Hi" --text "Hello"
yunori-mail events tailyunori-mail help lists every command; add --json for the API's JSON as-is.
Documentation
The full guides — API reference, webhooks, domains and receiving, safety and approval — are
at yunori.email/docs/agent-mail, and the
API is described as OpenAPI 3.1 at GET /api/v1/openapi.json on your server.
License
MIT
