Docs

How EMLbot works.

EMLbot gives each of your bots a real email inbox and an API key that can only do what you allow. This page explains the pieces, then gets a bot replying to mail in five steps.

OverviewQuickstartConceptsErrors and limits

Early accessEMLbot is onboarding a small group of teams. Paths and fields on these pages match the current API. Outbound sending runs in test mode while delivery is being connected: a send goes through every check and is saved to the inbox, but is not delivered yet.

What EMLbot is

EMLbot is an email API built for AI agents and the scripts around them. Each bot gets its own address, such as support-bot@bots.emlbot.com. Mail sent to that address is stored and announced to your code. Your bot reads it, labels it and replies over a small JSON API at https://api.emlbot.com/v0.

The part that makes it safe to hand to a bot is the key. Every API key carries its own rules: which inboxes it can see, what it may do there, who it may write to, how many sends it gets per day, and whether a person has to approve mail to anyone else. A bot that gets confused, or gets talked into something by an incoming email, still cannot step outside those rules.

How the pieces fit

There are four things to know. Each has a full explanation on the Concepts page.

  • Inbox. A real address on bots.emlbot.com. Its id is the address itself. It holds messages, groups them into threads and keeps labels on each message.
  • Key. A secret starting with emb_live_ that your bot sends as a bearer token. It has scopes (read, label, send, approve, admin) and can be limited to a list of inboxes. Inboxes outside that list simply do not exist for the key.
  • Send policy. Rules stored on the key and checked on every send and reply: an allowlist of recipients, a daily send cap, and an optional approval gate for anyone outside the allowlist.
  • Event. A record of something that happened: message.received, message.sent or message.approval_requested. Every event is written to a durable log you can read with a cursor, and is also pushed to your webhooks.

The life of one email

  1. Someone writes to support-bot@bots.emlbot.com. EMLbot stores the message, threads it with earlier mail, and labels it received and unread.
  2. A message.received event is written to the event log and delivered to every matching webhook, signed so you can prove it came from EMLbot.
  3. Your bot reads the message with its key and calls reply.
  4. The send policy checks every recipient. If all are allowed and the daily cap has room, the reply is sent and a message.sent event follows. If a recipient is outside the allowlist, the reply is either refused or parked for a person to approve, depending on the key.
  5. Your bot removes the unread label so the next run skips it.

Quickstart

Five steps from nothing to a bot that answers its mail. Every call is plain HTTPS with JSON, so curl is all you need. For every endpoint with example responses, see the full quickstart.

1. Get an admin key

Sign in to the console with Google. Sign-in is invite-only: the account owner invites teammates as owner, admin or viewer. On the API keys screen, create a key with the admin scope and no inbox list. You will use it to set things up, not to run the bot.

The full key is shown once. EMLbot stores only a hash of it, so put it in your secret manager straight away. Then check it works:

export EMLBOT_ADMIN_KEY="emb_live_..."
curl https://api.emlbot.com/v0/me -H "Authorization: Bearer $EMLBOT_ADMIN_KEY"

The answer lists the key's scopes and shows "admin": true.

2. Create an inbox

Creating inboxes takes the admin key. Pass a client_id of your own choosing and the call becomes safe to retry: the same client_id returns the same inbox instead of an error.

curl -X POST https://api.emlbot.com/v0/inboxes \
  -H "Authorization: Bearer $EMLBOT_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "support-bot", "display_name": "Support Bot", "client_id": "support-bot-v1"}'

The inbox id is the address, support-bot@bots.emlbot.com. Mail to it is accepted from now on.

3. Create a key for the bot

Give the bot its own key, limited to its inbox and to what it needs. The rules below let it read, label and send; write freely to anyone at yourco.com and to other EMLbot inboxes (team); send at most 100 messages per UTC day; and park anything addressed to someone else until a person approves it.

curl -X POST https://api.emlbot.com/v0/keys \
  -H "Authorization: Bearer $EMLBOT_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "support-bot",
    "scopes": ["read", "label", "send"],
    "inbox_ids": ["support-bot@bots.emlbot.com"],
    "allow_recipients": ["team", "@yourco.com"],
    "daily_send_cap": 100,
    "approval": "outside"
  }'

The response includes api_key, the only time the full key appears. Store it as the bot's secret:

export EMLBOT_KEY="emb_live_..."

4. Receive mail

Pick whichever suits how your bot runs. A webhook pushes each new message to your HTTPS endpoint as it arrives:

curl -X POST https://api.emlbot.com/v0/webhooks \
  -H "Authorization: Bearer $EMLBOT_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.yourco.com/emlbot", "event_types": ["message.received"], "inbox_ids": ["support-bot@bots.emlbot.com"]}'

The response carries the signing secret (whsec_...), again shown only once. Verify every delivery with it.

Or, if your bot runs on a schedule, ask for unread mail each time it wakes. Path ids are URL-encoded, so @ becomes %40:

curl "https://api.emlbot.com/v0/inboxes/support-bot%40bots.emlbot.com/messages?labels=unread&limit=20" \
  -H "Authorization: Bearer $EMLBOT_KEY"

Either way, the event log lets you catch up on anything you missed while your code was down.

5. Reply, then mark it handled

Reply to a message by its Message-ID, URL-encoded. The reply goes to the sender, stays in the same thread and gets a Re: subject automatically.

# MID is the URL-encoded Message-ID, for example %3CCAF7x9Lq2%40mail.yourco.com%3E
curl -X POST "https://api.emlbot.com/v0/inboxes/support-bot%40bots.emlbot.com/messages/$MID/reply" \
  -H "Authorization: Bearer $EMLBOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Thanks, we are on it."}'

curl -X PATCH "https://api.emlbot.com/v0/inboxes/support-bot%40bots.emlbot.com/messages/$MID" \
  -H "Authorization: Bearer $EMLBOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"add_labels": ["handled"], "remove_labels": ["unread"]}'

What the reply call can answer:

  • 200 with message_id and thread_id: every recipient was allowed and the reply was sent.
  • 202 with {"status": "pending_approval", "approval_id": "..."}: a recipient is outside the allowlist and the key uses approval: "outside". A person approves or rejects it in the console.
  • 403 forbidden naming the address: a recipient is outside the allowlist and the key has no approval gate.
  • 429 rate_limited: the key's daily send cap is spent until midnight UTC.

That is the whole loop. The errors page covers every other answer.

The API at a glance

Base URL https://api.emlbot.com/v0. JSON in and out, authenticated with Authorization: Bearer <key>. Each call is shown with an example on the quickstart.

ResourceCallsWho can call
MeGET /meAny valid key
InboxesPOST /inboxes, GET /inboxes, GET /inboxes/{inbox_id}, DELETE /inboxes/{inbox_id}Create and delete: admin. Read: read
MessagesGET .../messages, GET .../messages/{message_id}, .../raw, .../attachments/{attachment_id}, PATCH .../messages/{message_id}read; PATCH needs label
SendingPOST .../messages/send, POST .../messages/{message_id}/replysend, checked against the send policy
ThreadsGET .../threads, GET .../threads/{thread_id}read
ApprovalsGET /approvals, POST /approvals/{id}/approve, POST /approvals/{id}/rejectapprove
EventsGET /events?after=<seq>read
WebhooksPOST /webhooks, GET /webhooks, GET /webhooks/{id}, DELETE /webhooks/{id}, POST /webhooks/{id}/rotate-secretadmin
API keysPOST /keys, GET /keys, DELETE /keys/{id}admin
Audit logGET /auditadmin
UsersGET /users, POST /users, PATCH /users/{id}, DELETE /users/{id}The account owner, from the console
HealthGET https://api.emlbot.com/healthNo key needed
DomainsComing soonBots use bots.emlbot.com today

Read next

Coming soon

An MCP server with the same scopes and sending rules as the key, custom domains, and a live event stream are on the roadmap. Everything on these pages works today over the REST API.

Need a hand?

Email info@emlbot.com with what you're building.