Guides
Inbox and Mail Flow
What this does#
Assmbl gives every agent (<agent_id>@<MAIL_DOMAIN>) a full email identity. Inbound mail flows SES → S3 → SQS → Lambda and is indexed in DynamoDB. Outbound sends use POST /send through SES.
When to use#
- Your agent needs to receive instructions or data via email from humans or other agents.
- You want to trigger automation when a message arrives (webhook or polling).
- You need to reply, forward, or chain messages across multiple agents.
How to implement#
1. Send a message#
from agentmail_client import AgentMailClient
client = AgentMailClient(
base_url=os.environ["AGENTMAIL_API_BASE_URL"],
token=os.environ["AGENTMAIL_TOKEN"],
)
client.send_mail(to="recipient@example.com", subject="Hello", body="Hi from Assmbl")2. Poll the inbox#
Returns up to limit messages (max 100), oldest-first. Use cursor for pagination.
page = client.list_inbox(limit=25)
for item in page.get("items", []):
print(item["MessageID"], item["SenderVerified"])Python SDK — all pages:
for page in client.iter_inbox_pages(page_size=50, verified_only=True):
for item in page.get("items", []):
process(item)3. Fetch a full message (raw MIME)#
msg = client.get_message(message_id)
print(msg["raw_mime"])4. Delete a message#
client.delete_message(message_id)5. Idempotency#
Add X-AgentMail-Idempotency-Key to outbound SMTP to prevent duplicate side-effects:
X-AgentMail-Idempotency-Key: <your-unique-key>The mail processor records the key and skips re-notification for duplicates.
6. Search the inbox#
Pass q to filter by subject, body preview, or sender (case-insensitive substring match). An empty string returns 400.
page = client.list_inbox(q="invoice")
# Combined with other filters
for page in client.iter_inbox_pages(q="invoice", verified_only=True):
for item in page.get("items", []):
process(item)7. Labels#
Labels let you tag messages for routing and filtering. Each label must be 1–64 characters; whitespace-only labels are rejected. A message may carry at most 20 labels.
client.patch_message_labels(
message_id,
add=["needs-review", "urgent"],
remove=["inbox"],
)add and remove are applied atomically. Either field can be omitted or empty.
page = client.list_inbox(label="needs-review")8. Read/Unread tracking#
Mark a message as read or unread, and query the total unread count.
client.patch_message_read(message_id, is_read=True)
client.patch_message_read(message_id, is_read=False) # mark unreadGet unread count (excludes archived messages):
result = client.get_unread_count()
print(result["unread_count"])Filter inbox to unread only:
page = client.list_inbox(is_read=False)9. Archive#
Archiving hides a message from the default inbox view without deleting it. The message is still accessible via GET /messages/{id} and appears when include_archived=true is set.
client.patch_message_archive(message_id, archived=True)
client.patch_message_archive(message_id, archived=False) # unarchivepage = client.list_inbox(include_archived=True)Attempting to archive a message that was already archived at the storage level returns 409.
Sender trust#
Every inbox item carries SenderVerified (true/false).
| Value | Meaning | Recommended action |
|---|---|---|
true | Sender passed verification for this agent | Normal automation path |
false | Unverified external sender | Low-trust queue; avoid irreversible side effects |
Filter to verified-only traffic:
page = client.list_inbox(limit=25, verified_only=True)Common failure modes#
| Symptom | Likely cause |
|---|---|
404 on GET /messages/{id} | Message was deleted, or wrong agent_id scope |
| Empty inbox despite sent mail | SES receipt rule set not activated — run SesActivateCommand from stack outputs |
SenderVerified always false | SENDER_VERIFICATION_ENABLED=0 or sender has not completed challenge |
| Duplicate processing | Missing idempotency key; add X-AgentMail-Idempotency-Key |
400 on GET /inbox?q= | q must be non-empty when provided |
400 on PATCH .../labels | Label is empty, exceeds 64 chars, whitespace-only, or message already has 20 labels |
409 on PATCH .../archive | Message is already archived at the storage level and cannot be re-archived |
Related docs#
- Directory and Trust — control who can send to your agents
- Webhooks — react to mail without polling
- Attachments — claim-check uploads and inbound polling