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
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.sentormessage.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
- Someone writes to
support-bot@bots.emlbot.com. EMLbot stores the message, threads it with earlier mail, and labels itreceivedandunread. - A
message.receivedevent is written to the event log and delivered to every matching webhook, signed so you can prove it came from EMLbot. - Your bot reads the message with its key and calls reply.
- The send policy checks every recipient. If all are allowed and the daily cap has room, the reply is sent and a
message.sentevent follows. If a recipient is outside the allowlist, the reply is either refused or parked for a person to approve, depending on the key. - Your bot removes the
unreadlabel 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:
200withmessage_idandthread_id: every recipient was allowed and the reply was sent.202with{"status": "pending_approval", "approval_id": "..."}: a recipient is outside the allowlist and the key usesapproval: "outside". A person approves or rejects it in the console.403 forbiddennaming 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.
| Resource | Calls | Who can call |
|---|---|---|
| Me | GET /me | Any valid key |
| Inboxes | POST /inboxes, GET /inboxes, GET /inboxes/{inbox_id}, DELETE /inboxes/{inbox_id} | Create and delete: admin. Read: read |
| Messages | GET .../messages, GET .../messages/{message_id}, .../raw, .../attachments/{attachment_id}, PATCH .../messages/{message_id} | read; PATCH needs label |
| Sending | POST .../messages/send, POST .../messages/{message_id}/reply | send, checked against the send policy |
| Threads | GET .../threads, GET .../threads/{thread_id} | read |
| Approvals | GET /approvals, POST /approvals/{id}/approve, POST /approvals/{id}/reject | approve |
| Events | GET /events?after=<seq> | read |
| Webhooks | POST /webhooks, GET /webhooks, GET /webhooks/{id}, DELETE /webhooks/{id}, POST /webhooks/{id}/rotate-secret | admin |
| API keys | POST /keys, GET /keys, DELETE /keys/{id} | admin |
| Audit log | GET /audit | admin |
| Users | GET /users, POST /users, PATCH /users/{id}, DELETE /users/{id} | The account owner, from the console |
| Health | GET https://api.emlbot.com/health | No key needed |
| Domains | Coming soon | Bots 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.