Guides
Agents and MCP
Give an AI agent an email address of its own. It reads and writes through its own key, and every message it sends is held to rules you set: anything outside them waits for a person to approve.
How it works
- You create the agent in the console under Agents. It gets a real mailbox on your domain and a key of its own.
- The key holds
agent:mailalone and is bound to that one agent. It reaches that mailbox and nothing else in your account. - The agent may always write to colleagues at its own domain. Beyond that you choose: anyone, only people who wrote to it first, or an allow list, within a daily limit. Or hold every message for approval.
- A message outside the rules is not refused, it waits. A person approves it (and may edit it first) or turns it down. Approving twice sends once.
- Every read and every send is on the agent's record.
Connect with MCP
The Rykon Mail MCP server is one file with no dependencies. It runs on Node 18 or later, speaks MCP over stdio, and calls only the agent inbox API below with the agent's key. It is short enough to read before you trust it.
curl -fsSLo rykon-mail-mcp.mjs https://rykonmail.com/assets/agents/rykon-mail-mcp.mjs
claude mcp add rykon-mail \
-e RYKON_MAIL_AGENT_KEY=rmk_live_... \
-- node "$PWD/rykon-mail-mcp.mjs"Any other MCP client takes the same command and environment, usually as JSON:
{
"mcpServers": {
"rykon-mail": {
"command": "node",
"args": ["/path/to/rykon-mail-mcp.mjs"],
"env": { "RYKON_MAIL_AGENT_KEY": "rmk_live_..." }
}
}
}RYKON_MAIL_API overrides the API address, which defaults to https://app.rykonmail.com/api/agent/v1.
| Tool | Does |
|---|---|
| whoami | The agent’s name, address and the rules its sending is held to. |
| inbox | Messages in the inbox, newest first; unread only by default. |
| read_message | One message in full. Reading marks it read and is recorded. |
| reply | Reply in the same thread, to whoever sent the message. |
| send_message | Write a new message. Anything outside the rules waits for a person. |
| message_status | Whether a held message went out, is still waiting, or was turned down. |
Held means held
The agent inbox API
The same five calls the MCP server makes, for an agent you run yourself. Base URL https://app.rykonmail.com/api/agent/v1, authenticated with the agent's key as Authorization: Bearer. Errors have the usual shape, and each agent key has its own allowance.
/meThe agent itself
The agent’s name and address, and the rules its sending is held to, with a sentence that explains them which you can hand to the model as it is.
curl https://app.rykonmail.com/api/agent/v1/me \
-H "Authorization: Bearer $RYKON_MAIL_AGENT_KEY"{
"name": "Support agent",
"address": "support-agent@yourcompany.com",
"state": "active",
"rules": {
"sendTo": "repliers",
"allowList": [],
"dailyLimit": 50,
"approveAll": false,
"explained": "You may write to people at yourcompany.com, and to people who have written to you, up to 50 messages a day. Anything else waits for a person."
}
}/messagesList the inbox
Messages in the agent’s inbox, newest first.
Query parameters
- unseenbooleanoptional
- true for only the messages not yet read. Default false.
- limitintegeroptional
- How many, 1 to 100. Default 20.
curl "https://app.rykonmail.com/api/agent/v1/messages?unseen=true&limit=20" \
-H "Authorization: Bearer $RYKON_MAIL_AGENT_KEY"{
"messages": [
{
"uid": 41,
"subject": "Question about my order",
"from": {
"name": "Jordan Lee",
"address": "jordan@example.com"
},
"date": "2026-10-30T09:12:44.000Z",
"seen": false
}
]
}/messages/{uid}Read a message
One message in full, as text. Reading it marks it seen and puts it on the agent’s record. A very long message is cut, and says so with truncated.
Path parameters
- uidintegerrequired
- The message’s uid from the inbox list.
curl https://app.rykonmail.com/api/agent/v1/messages/41 \
-H "Authorization: Bearer $RYKON_MAIL_AGENT_KEY"{
"uid": 41,
"from": {
"name": "Jordan Lee",
"address": "jordan@example.com"
},
"to": [
{
"name": "Support agent",
"address": "support-agent@yourcompany.com"
}
],
"cc": [],
"date": "2026-10-30T09:12:44.000Z",
"subject": "Question about my order",
"text": "Hi, my order 1042 has not arrived yet. Can you check where it is?",
"truncated": false
}/messagesSend or reply
Write a new message, or reply to one in the inbox by its uid: the recipient, subject and threading are then taken from that message unless you give them. A message inside the rules is sent and answered 200; anything else waits for a person and is answered 202 with state pending and the reason.
Body
- tostring[]optional
- Up to 20 addresses. Required unless replyTo is given.
- ccstring[]optional
- Up to 20 addresses.
- subjectstringoptional
- Up to 300 characters. Required unless replyTo is given.
- textstringrequired
- The plain text body.
- replyTointegeroptional
- The uid of the message this answers.
curl -X POST https://app.rykonmail.com/api/agent/v1/messages \
-H "Authorization: Bearer $RYKON_MAIL_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": [
"sam@example.org"
],
"subject": "Your quote from Your Company",
"text": "Hi Sam, as promised on the phone, your quote is below."
}'{
"id": "3b5f0c2e-8d41-4a8e-b7a2-61f0d9e4c7aa",
"state": "pending",
"heldBecause": "sam@example.org has not written to it first"
}/outbox/{id}Where a message stands
Whether a message the agent asked to send is pending, sent or rejected, and whether a person edited it before it went.
Path parameters
- idstringrequired
- The id a send returned.
curl https://app.rykonmail.com/api/agent/v1/outbox/3b5f0c2e-8d41-4a8e-b7a2-61f0d9e4c7aa \
-H "Authorization: Bearer $RYKON_MAIL_AGENT_KEY"{
"id": "3b5f0c2e-8d41-4a8e-b7a2-61f0d9e4c7aa",
"state": "sent",
"recipients": [
"sam@example.org"
],
"subject": "Your quote from Your Company",
"held_because": "sam@example.org has not written to it first",
"edited": false,
"decided_at": "2026-10-30T09:20:03.118Z",
"error": null,
"created_at": "2026-10-30T09:14:51.902Z",
"sent_at": "2026-10-30T09:20:04.511Z"
}New mail notifications
Instead of polling, an agent can be set in the console to notify your endpoint when mail arrives. Each new message is posted once as JSON, signed in the x-rykonmail-signature header with the agent's notification secret: t= the time in Unix seconds, v1= the hex HMAC-SHA256 of the time, a full stop and the raw body.
{
"type": "message.received",
"agent": { "id": "e2a7c9d4-...", "address": "support-agent@yourcompany.com" },
"message": {
"uid": 41,
"messageId": "<CAF1x9@mail.example.com>",
"from": "jordan@example.com",
"subject": "Question about my order",
"date": "2026-10-30T09:12:44.000Z"
}
}import { createHmac, timingSafeEqual } from 'node:crypto';
// header: the x-rykonmail-signature value, "t=<seconds>,v1=<hex>"
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}Managing agents from your account
With an account key holding mailboxes:read or mailboxes:write, you can create agents, set their sending rules, and approve or reject the messages waiting for a person, from your own tools. Approving sends once, however many times it is called.
- GET
/agentsList agents - POST
/agentsCreate an agent - GET
/agents/approvalsList messages waiting for approval - POST
/agents/outbox/{outboxId}/approveApprove a held message - POST
/agents/outbox/{outboxId}/rejectReject a held message - GET
/agents/{id}Get an agent - PUT
/agents/{id}Set an agent's sending rules - DEL
/agents/{id}Remove an agent