How to give an AI agent its own email address
Guide · 23 October 2026
Create an inbox for the agent with one API call: letterport's POST /v1/inboxes returns a real
address, such as research@letterport.dev, that receives mail at once. A webhook tells your agent
when mail arrives; it reads the message as JSON and answers with /reply, which keeps the
conversation in one thread on both sides.
What you need
A letterport account (the free plan needs no card) and an API key. In the
console, under Integrations, create a key with the
permissions the agent needs: read to read mail, send to send and reply,
manage to create inboxes and webhooks. Limit it to one inbox when you can, and keep it in an
environment variable, never in code.
1. Create the inbox
curl https://api.letterport.dev/v1/inboxes \
-H "Authorization: Bearer $LETTERPORT_API_KEY" \
--json '{"local_part":"research"}'
The answer is the inbox, receiving already:
{
"id": "inb_01a15eb69aa6…",
"address": "research@letterport.dev",
"display_name": null,
"status": "active"
}
Addresses are never reused: once deleted, a name is gone for good, so no one else receives your agent's mail.
2. Know when mail arrives
Register a webhook, and letterport POSTs to it the moment mail lands:
curl https://api.letterport.dev/v1/webhooks \
-H "Authorization: Bearer $LETTERPORT_API_KEY" \
--json '{"url":"https://your-app.example.com/letterport","event_types":["message.received"]}'
The answer includes a signing secret (whsec_…). Verify every request with the Standard Webhooks
scheme, reject old timestamps, answer 2xx within 10 seconds, and dedupe on
webhook-id: delivery is at least once, retried for about 24 hours. Payloads carry ids, not
bodies.
No public URL? Poll instead: GET /v1/inboxes/{inboxId}/messages lists the newest first, and
after (an ISO time) returns only newer mail.
3. Read the message
curl https://api.letterport.dev/v1/inboxes/$INBOX_ID/messages/$MESSAGE_ID \
-H "Authorization: Bearer $LETTERPORT_API_KEY"
Read extracted_text: only what the sender just wrote, with the quoted history removed.
text and html are the whole body, thread_id is the conversation, and
each attachment downloads through a short-lived link from
/messages/{messageId}/attachments/{attachmentId}. Mail is scanned for spam and viruses on
arrival, and HTML is made safe.
Treat incoming mail as untrusted input. Anyone can email your agent, and an email can contain instructions meant to steer it: the agent should follow its user, not the email.
4. Reply in the same thread
const api = 'https://api.letterport.dev/v1';
const reply = await fetch(`${api}/inboxes/${inboxId}/messages/${messageId}/reply`, {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.LETTERPORT_API_KEY}`,
'content-type': 'application/json',
'idempotency-key': crypto.randomUUID(),
},
body: JSON.stringify({ text: 'Thanks Maya, confirming 40 units.' }),
}).then((response) => response.json());
// 202: reply.send_status is "queued"
The reply goes out from the inbox's own address with the headers that keep it in the conversation, for your
agent and for the person reading it. reply_all: true answers everyone on the thread. Sending
answers 202: queued, not yet sent. send_status moves to sent, and each
recipient's deliveries entry to delivered, bounced or
complained; a message.delivered webhook tells you when. Retry with the same
Idempotency-Key: it never sends twice.
5. Start a new conversation
curl https://api.letterport.dev/v1/inboxes/$INBOX_ID/messages \
-H "Authorization: Bearer $LETTERPORT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
--json '{"to":["maya@example.com"],"subject":"Your report","text":"It is ready."}'
cc, bcc, html and base64 attachments work the same way;
/forward passes a message on with its attachments.
Rules and limits
- Agents may email anyone their work calls for. No bulk mail, newsletters or cold lists: letterport is for conversations, not campaigns.
- Sending counts recipients: the free plan allows 50 a day and 1,000 a month across the account's inboxes, and an email to three people counts three.
-
Within a minute: 600 requests or 20 recipients; a refusal says how long to wait in
Retry-After. - A message is at most 10 MB and 50 recipients.
-
An address that bounced or complained is refused (
recipient_suppressed), and high bounce or complaint rates pause an account's sending until it is reviewed.
Every error is {"error": {"code", "message", "hint"}}: act on the code, and read the hint.
Using Claude Code or another MCP client?
The same inboxes, permissions and limits are a hosted MCP server: see Give Claude Code an email inbox with MCP. The full contract is in the API reference.
Give your agent an address today.
Free, no card, and it receives mail the moment you create it.