API Documentation

Base URL: https://api.theagentmail.net

Getting started

AgentMail gives your AI agent its own email address. Here's how to set it up.

1. Sign in and create an org

Sign in with your personal email (Gmail, Outlook, ProtonMail, etc.). Then create an organization from the dashboard. You'll need an org admin token for the next step -- generate one from the Admin section.

2. Create an email account for your agent

curl -X POST https://api.theagentmail.net/v1/accounts \
  -H "Authorization: Bearer am_org_..." \
  -H "Content-Type: application/json" \
  -d '{"address": "my-agent@theagentmail.net"}'

3. Create a token scoped to that account

Use the account ID from the previous response. This token only has access to this one mailbox -- safe to give to your agent.

curl -X POST https://api.theagentmail.net/v1/accounts/ACCOUNT_ID/api-keys \
  -H "Authorization: Bearer am_org_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "my-agent-key"}'

Save the key from the response. It's only shown once.

4. Give your agent the token and this docs page

Pass the account token and https://api.theagentmail.net/docs to your AI agent. It can read the API reference and start sending and receiving email on its own.

Authentication

All API endpoints require a Bearer token in the Authorization header.

Authorization: Bearer am_...

There are two types of tokens:

Org tokens

Full access to all accounts in your organization. Create and delete accounts, send and receive email, manage webhooks, check karma. Created from the dashboard.

Account tokens

Scoped to a single email account. Can send and receive email, manage webhooks, and view account info. Cannot access other accounts or create new ones (returns 403). Created programmatically via POST /v1/accounts/:id/api-keys.

Account tokens can use shortcut routes that omit the account ID -- the server infers it from the token. For example, POST /v1/messages instead of POST /v1/accounts/:accountId/messages. See each endpoint section for the shortcut path.

Accounts

POST/v1/accounts-10 karma

Create a new email account. Costs 10 karma.

Request body

{
  "address": "my-agent@theagentmail.net",
  "displayName": "My Agent"  // optional
}

Response (201)

{
  "data": {
    "id": "abc123",
    "address": "my-agent@theagentmail.net",
    "displayName": "My Agent",
    "createdAt": 1709136000000
  }
}

Reserved addresses (uri, support) cannot be created.

GET/v1/accounts

List all email accounts in your organization.

Response

{
  "data": [
    {
      "id": "abc123",
      "address": "my-agent@theagentmail.net",
      "displayName": "My Agent",
      "createdAt": 1709136000000
    }
  ]
}
GET/v1/accounts/:accountId

Get details for a specific account. With an account token: GET /v1/account

DELETE/v1/accounts/:accountId+5 karma

Delete an email account. Refunds 5 karma. With an account token: DELETE /v1/account

Messages are retained for reference. The address becomes available for reuse.

Messages

POST/v1/accounts/:accountId/messages-1 karma

Send an email from this account. Costs 1 karma. With an account token: POST /v1/messages

Request body

{
  "to": ["human@example.com"],
  "cc": ["other@example.com"],       // optional
  "bcc": ["hidden@example.com"],     // optional
  "subject": "Hello from my agent",
  "text": "Plain text body",
  "html": "<p>HTML body</p>",        // optional
  "inReplyTo": "<msgid@example>",    // optional, for threading
  "references": "<msgid@example>",   // optional
  "attachments": [                   // optional
    {
      "filename": "report.pdf",
      "contentType": "application/pdf",
      "content": "base64-encoded-data"
    }
  ]
}

Response (201)

{
  "data": {
    "id": "msg_123",
    "from": "my-agent@theagentmail.net",
    "to": ["human@example.com"],
    "subject": "Hello from my agent",
    "direction": "outbound",
    "status": "queued",
    "timestamp": 1709136000000
  }
}
GET/v1/accounts/:accountId/messages

List all messages (inbound and outbound) for this account, sorted by most recent first. With an account token: GET /v1/messages

Response

{
  "data": [
    {
      "id": "msg_123",
      "from": "sender@gmail.com",
      "to": ["my-agent@theagentmail.net"],
      "subject": "Re: Hello",
      "direction": "inbound",
      "status": "received",
      "timestamp": 1709137000000
    }
  ]
}
GET/v1/accounts/:accountId/messages/:messageId

Get a single message with full body content and attachment metadata. With an account token: GET /v1/messages/:messageId

Response

{
  "data": {
    "id": "msg_123",
    "from": "sender@gmail.com",
    "to": ["my-agent@theagentmail.net"],
    "subject": "Re: Hello",
    "text": "Full message body...",
    "html": "<p>Full HTML body...</p>",
    "direction": "inbound",
    "status": "received",
    "timestamp": 1709137000000,
    "attachments": [
      {
        "id": "att_456",
        "filename": "photo.jpg",
        "contentType": "image/jpeg",
        "size": 245000
      }
    ]
  }
}

Attachments

GET/v1/accounts/:accountId/messages/:messageId/attachments/:attachmentId

Get a signed download URL for an attachment. The URL is valid for 1 hour. With an account token: GET /v1/messages/:messageId/attachments/:attachmentId

Response

{
  "data": {
    "url": "https://storage.googleapis.com/...",
    "filename": "photo.jpg",
    "contentType": "image/jpeg",
    "expiresAt": 1709140600000
  }
}

Webhooks

Register a webhook URL to get notified when your account receives email. We'll POST to your URL within seconds of delivery.

POST/v1/accounts/:accountId/webhooks

Register a webhook for inbound email notifications. With an account token: POST /v1/webhooks

Request body

{
  "url": "https://my-agent.example.com/inbox"
}

Response (201)

{
  "data": {
    "id": "wh_789",
    "url": "https://my-agent.example.com/inbox",
    "secret": "whsec_abc123...",
    "active": true,
    "createdAt": 1709136000000
  }
}

Save the secret -- it's only returned on creation. Use it to verify webhook signatures.

GET/v1/accounts/:accountId/webhooks

List all webhooks for this account. With an account token: GET /v1/webhooks

DELETE/v1/accounts/:accountId/webhooks/:webhookId

Delete a webhook. With an account token: DELETE /v1/webhooks/:webhookId

Webhook delivery format

When an email arrives, we POST a JSON payload to your webhook URL:

{
  "messageId": "msg_123",
  "accountId": "abc123",
  "from": "sender@gmail.com",
  "to": ["my-agent@theagentmail.net"],
  "subject": "Hello",
  "text": "Message body...",
  "html": "<p>Message body...</p>",
  "timestamp": 1709137000000
}

Signature verification

Every webhook delivery includes two headers:

  • X-AgentMail-Signature — HMAC-SHA256 hex digest of the request body
  • X-AgentMail-Timestamp — millisecond timestamp of when the delivery was sent

Verify the signature using your webhook secret, and reject requests with timestamps older than 5 minutes to prevent replay attacks:

import { createHmac } from "crypto";

const verifyWebhook = (body: string, signature: string, timestamp: string, secret: string) => {
  const age = Date.now() - Number(timestamp);
  if (age > 5 * 60 * 1000) return false;
  const expected = createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return signature === expected;
};

// In your webhook handler:
// const body = await req.text();
// const sig = req.headers.get("x-agentmail-signature");
// const ts = req.headers.get("x-agentmail-timestamp");
// if (!verifyWebhook(body, sig, ts, YOUR_WEBHOOK_SECRET)) {
//   return new Response("Unauthorized", { status: 401 });
// }

Karma

Karma is a credit system that keeps the shared domain clean. Sending costs karma. Receiving from real people earns it back. Agents that have genuine conversations sustain themselves. Spammers run out and get blocked.

You start with 100 karma when you pay. That's 100 sends, or 10 accounts, or some mix.

money_paid

Purchase karma credits

+100
email_received

Reply from a trusted domain (once per sender until you reply back)

+2
account_deleted

Delete an email account (partial refund)

+5
email_sent

Send an email

-1
account_created

Create a new email account

-10

Trusted domains

Karma for email_received is only awarded when the sender uses a trusted email provider: Gmail, Outlook, Yahoo, iCloud, ProtonMail, Fastmail, Hey, and similar personal email services. Emails from throwaway or bulk domains don't earn karma.

Anti-gaming

You only earn karma once per sender until your agent replies back. If someone sends you 10 emails without you replying, you get +2 karma total, not 20.

GET/v1/karma

Get your organization's karma balance and event history.

Response

{
  "data": {
    "balance": 87,
    "events": [
      {
        "id": "evt_1",
        "type": "money_paid",
        "amount": 100,
        "timestamp": 1709136000000
      },
      {
        "id": "evt_2",
        "type": "account_created",
        "amount": -10,
        "timestamp": 1709136100000
      }
    ]
  }
}

Organizations

Each organization has one billing user (the creator) and can have additional admins. Karma and accounts belong to the organization, not individual users.

Organizations are managed from the dashboard. By default, each user can create one organization. Contact support@theagentmail.net to create additional organizations.

Account API keys

POST/v1/accounts/:accountId/api-keys

Create a token scoped to a single account. Useful for giving an agent access to only its own mailbox without exposing org-level access.

Request body

{
  "name": "My agent's key"  // optional
}

Response (201)

{
  "data": {
    "id": "key_123",
    "key": "am_abc123...",
    "prefix": "am_abc1",
    "name": "My agent's key",
    "createdAt": 1709136000000
  }
}

The full key is only returned once. Store it securely.

Errors

All errors return a JSON object with an error message and code:

{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE"
}
401UNAUTHORIZEDMissing or invalid API key / token
402KARMA_LOWInsufficient karma for this action
403FORBIDDENAccount-scoped token accessing org-level endpoint
404NOT_FOUNDResource not found
409CONFLICTAddress already taken
500INTERNAL_ERRORSomething went wrong on our end

TypeScript SDK

If you prefer typed method calls over raw HTTP, use the TypeScript SDK.

import { createClient } from "@agentmail/sdk";

// With an account-scoped token (no accountId needed)
const mail = createClient({ apiKey: "am_..." });

// Send an email
await mail.messages.send({
  to: ["human@example.com"],
  subject: "Hello from my agent",
  text: "This email was sent by an AI agent.",
});

// Check inbox
const messages = await mail.messages.list();

// Register a webhook
await mail.webhooks.create({
  url: "https://my-agent.example.com/inbox",
});

// Get account info
const account = await mail.accounts.get();

// With an org-level token (accountId required)
const orgMail = createClient({ apiKey: "am_org_..." });
await orgMail.messages.send(accountId, {
  to: ["human@example.com"],
  subject: "Hello",
  text: "Sent via org token.",
});

OpenCode skill

If your agent runs on OpenCode, install the AgentMail skill and it learns the full API automatically. No SDK needed.

opencode skill install agentmail