Receive email
Mail that reaches an agent is stored as a received message, grouped into a thread, scanned for prompt injection (within your plan’s limits) and announced as an event. You can read it through the API, a webhook, or the event stream.
Where mail arrives
An agent named hello in the workspace acme is hello@acme.mails.ai. It receives mail at hello.acme@in.mails.ai. Unless you set reply_to on a send, every email the agent sends carries that address as its Reply-To, so a reply from any mail app comes back to the agent. To write to an agent first, use the in.mails.ai address.
An agent on your own verified domain sends from its own address there, and its Reply-To is still its receiving address on in.mails.ai (for billing@acme.com, billing.acme_com@in.mails.ai), so replies come back to the agent. The DNS records mails.ai asks for cover sending only: mail written straight to billing@acme.com goes wherever that domain’s mail is delivered today, unless the agent is in forwarding mode and your mail service forwards it to the agent’s forwarding_address.
Replies and new mail
Each received message produces one of these events:
| Event | When |
|---|---|
reply.received | The message answers one of this agent’s own sends: its In-Reply-To header names a message the agent sent. |
message.received | Any other message: first contact, or mail that does not answer one of the agent’s sends. |
message.received.unauthenticated | A forged sender: the From domain publishes a DMARC policy of quarantine or reject, and no DKIM signature aligned with it verifies (SPF is not checked). This wins over reply.received. |
message.received.over_limit | The workspace was over a receiving limit, or its subscription was not active: the message is not scanned, and the event carries no subject or body. See the receiving limits. |
An agent that only acts on answers can listen for reply.received and ignore the rest.
Threads
A message joins the thread of the message its In-Reply-To header names. When a mail app dropped that header, the message joins the agent’s open thread with the same subject, if its sender is already in that thread and the subject has at least four characters. Otherwise it starts a new thread, so two people who happen to write with the same subject never share one. A thread holds one agent’s mail only. A message that joins a thread by its subject is still a message.received, not a reply.
Read received mail
GET /List received messages. Needs thev1/ messages/ received readscope.GET /Retrieve a received message. Needs thev1/ messages/ received/ {id} readscope.GET /List a received message's attachments. Needs thev1/ messages/ received/ {id}/ attachments readscope.GET /Download an attachment. Needs thev1/ messages/ received/ {id}/ attachments/ {attachment_id} readscope.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const { data } = await client.received.list({ limit: 10 });
for (const message of data) {
console.log(message.from.address, message.subject);
}
const message = await client.received.get(data[0].id);
console.log(message.extracted_text);from mailsai import Client
client = Client()
page = client.list_received(limit=10)
for message in page["data"]:
print(message["from"]["address"], message["subject"])
message = client.get_received(page["data"][0]["id"])
print(message["extracted_text"])curl "https://api.mails.ai/v1/messages/received?limit=10" \
-H "Authorization: Bearer $MAILS_API_KEY"A list returns short excerpts; fetch one message for its whole body. The fields an agent reads most:
| Field | Description |
|---|---|
extracted_text | string Reply text with quoted history stripped. |
body_text | string Full body — single-message GET only. |
has_attachment | boolean Required. True when the message carries at least one file attachment (images embedded in the HTML body do not count). |
attachments | object[] Required. Every part of the message that is a file, embedded images included. Download one from GET /v1/messages/received/{id}/attachments/{attachment_id}. |
attachments[].id | string Required. Attachment id (att_…), unique within the message. |
attachments[].filename | string Required. The file's name, cleaned: no path, no control characters, at most 255 characters. |
attachments[].content_type | string Required. The MIME type the sender declared, e.g. application/pdf. |
attachments[].size | integer Required. Size in bytes of the decoded file. |
attachments[].content_id | string | null Required. The part's Content-ID without angle brackets, which the HTML body references as cid:. |
attachments[].inline | boolean Required. True for an image embedded in the HTML body rather than attached as a file. |
parse_status | string Required. quota_skipped: not classified or scanned, because the workspace was over a limit (over_limit says which). It is still in a thread, so it can be trashed and deleted, and raised message.received.over_limit instead of message.received. One of: parsed, quarantined, pending, failed, quota_skipped. |
raw_url | string Path to the message as an .eml file — single-message GET only. The exact message as received when it was kept, else one rebuilt from the stored fields. |
Files are covered in Attachments, and finding old mail in Search.
Unsafe mail is marked, not dropped
Inbound mail within your plan’s limits is scanned for prompt injection before your agent sees it. Its events carry an injection_score from 0 to 1. At 0.95 or above the message is quarantined: it is still delivered, with quarantined: true on the event and parse_status set to quarantined on the message. Decide in your own code what an agent may do with a quarantined message. The knowledge base explains the score.
Try it with a test key
With a mk_test_ key, POST /v1/test/inbound delivers a made-up email to one of your agents. It runs the real scan, threads the message, stores it and emits the same event and webhook real mail would. Pass in_reply_to_message_id with the id of an earlier test send to get a reply.received.
curl -X POST https://api.mails.ai/v1/test/inbound \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "hello",
"from": "customer@example.com",
"subject": "Where is my order?",
"body_text": "Hi, I ordered on Monday and have not had a shipping email yet."
}'Next steps
Was this page helpful?