Best practices for agent email integration.
The entire integration is 5 lines in your agent's system prompt or config:
You have an email address for contacting your operator. API: https://sixel.email/v1 Token: sm_live_xxxxx POST /v1/send to email. GET /v1/inbox to check for replies. Poll /v1/inbox every 60s while waiting.
That's it. Don't over-specify. The agent figures out when to email (stuck, needs approval, has results) and what to say. You're adding a capability, not a workflow.
Every GET /v1/inbox call updates your agent's heartbeat.
If the agent stops polling, you get a notification email. This means you don't
need a separate health check — inbox polling is the health check.
Each agent can only email one address: the one you set during setup. This is the leash. Your agent can't be tricked into emailing anyone else, and it can't be spammed by strangers. Inbound email from any other sender is rejected at the edge before it touches the API.
The most useful pattern: agent does work, emails you when done or stuck,
then polls /v1/inbox in a loop until you reply. The agent is "asleep" —
consuming no inference, just one HTTP GET per minute.
# Pseudocode: sleep until reply
send_email("Finished the analysis. Results attached. What next?")
while True:
response = poll_inbox()
if response.messages:
handle(response.messages[0])
break
sleep(60)
This turns a synchronous back-and-forth into an async conversation. You reply when you're ready — hours later, next morning, whenever. The agent wakes up and continues.
Long-running agents will hit context limits. When the context compresses or the session restarts, the agent loses awareness of what it was waiting for. Two mitigations:
Your agent emails the operator. No reply for hours. What should it do?
Don't assume the operator saw the message. Don't send follow-ups. The agent should have a fallback mode: continue with non-destructive work it can do without approval, or gracefully idle (poll the inbox, do nothing else). The worst outcome is an agent that escalates its own urgency — sending repeated messages, making assumptions, or taking actions it was waiting for approval on.
Include an attachments array in your POST /v1/send request.
Each attachment is a JSON object with filename and content (base64-encoded).
{
"subject": "Analysis results",
"body": "Here are the results.",
"attachments": [
{"filename": "results.csv", "content": "aWQsbmFtZSxzY29yZQ=="}
]
}
Limits: 10MB total decoded size, max 10 files per message.
GET /v1/inbox includes attachment metadata on each message:
"attachments": [
{"id": "uuid", "filename": "photo.jpg", "mime_type": "image/jpeg", "size_bytes": 164883}
]
Download the content with GET /v1/inbox/{message_id}/attachments/{attachment_id}.
Returns raw bytes with the correct Content-Type header.
If you run multiple agent sessions (different projects, different specializations), they can share a single inbox and coordinate responses without a central orchestrator.
We published a reference architecture: sixel-et/sixel-teams. The core pattern is hubless coordination — a lightweight watcher handles email I/O, wakes sessions, collects contributions, and sends one assembled reply. No framework, no shared context window, no LLM calls in the coordination layer.
The reference implementation uses tmux and bash scripts. The architecture is harness-agnostic — the same watcher/contributor/assembler pattern works whether your agent sessions are tmux panes, Docker containers, or framework-managed processes. Adapt the coordination layer to your stack; the email integration stays the same.
The leash (one allowed contact) means the agent can't be weaponized for spam or social engineering. But the inbound side needs protection too — can someone send your agent a malicious email?
p=reject DMARC policy.Door Knock is opt-in — toggle it during signup or on your
/account page. Without it, emails from your allowed contact are accepted
directly. With it enabled:
Every outbound email from your agent includes a Reply-To address with a
single-use nonce: agent+nonce@sixel.email. When you hit reply, the nonce
validates your response automatically. No codes, no apps, no extra steps.
If you want to email your agent without a prior message to reply to (a "knock"),
just send to agent@sixel.email. The agent ignores the content but
auto-replies with a fresh nonce in the Reply-To. Reply to that, and your
message goes through. Three emails, zero friction.
This means an attacker with a compromised email account can send a knock, but the auto-reply goes to your inbox — they'd need to also compromise your inbox to see the nonce. If that's happened, you have bigger problems than agent email.
Set up a kill switch from /account. You get an allstop email address
that deactivates the channel instantly. Save it as a phone contact — the setup
page gives you a QR code. If anything goes wrong, send one email to that address
and the channel shuts down. Reactivation requires a live session or the account dashboard.
sixel.email delivers message content as-is. We do not sanitize, filter, or scan email bodies for prompt injection. This is deliberate.
The email body is untrusted input — even from your allowed contact. A forwarded email, an auto-reply, a mailing list digest, or a compromised account can all produce content that looks like instructions to an LLM. Your agent must treat every inbound message the same way it treats any user input: as data, not as commands.
If your framework supports it, pass email bodies as user messages, not system messages. The distinction matters for models that weight system instructions more heavily than user input.
Some agents run in environments without persistent filesystem access — serverless functions, ephemeral containers, or framework-managed processes that restart frequently. The inbox API is designed for this:
GET /v1/inbox returns unread messages
and marks them read atomically. If your agent restarts, it won't re-process messages
it already fetched (unless the fetch failed before the agent acted on them).If you can't persist a seen-ID list (see Common mistakes below), rely on the server-side read tracking. The tradeoff: if a fetch succeeds but your agent crashes before processing, the message is marked read and won't reappear. For critical workflows, persist the message ID before acting on it.
The API rate-limits inbox polls. 60 seconds is the sweet spot. Polling every second doesn't get you replies faster — it gets you rate-limited.
If your agent restarts and re-reads the inbox, it will see messages it already handled (the inbox marks messages as read on fetch, but network issues can cause re-delivery). Keep a local list of message IDs you've processed. Check it before acting on any message.
Subjects aren't required by the API, but your email client will bury subjectless messages. Always include a subject — your future self will thank you when scanning a long thread.
Every /v1/inbox response includes credits_remaining.
If your agent notices credits getting low, it should tell you. Don't let it discover
it's out of credits when it has something important to say.
Email is async. Your agent should send one well-composed message, then wait. Don't send five messages in a row. Don't poll for 10 seconds and then send a follow-up asking if you got the first one.
If your human pastes an image into an email, the body field can contain 200KB+ of base64-encoded image data. An agent that reads or greps this content without checking can enter a hot loop — regex engines hit catastrophic backtracking on binary data, and the session becomes unrecoverable.
No. Only the one allowed contact you set during setup. This is by design.
Yes — from your /account page. Changing the contact forces an API key
rotation and clears your message history. This is a security measure: if the change was
unauthorized, the old key stops working immediately and the attacker can't access
prior messages.
If the agent stops polling /v1/inbox, you'll get a notification email
after a configurable timeout. When the agent comes back and resumes polling,
you'll get a recovery notification.
One address = one agent = one API key. But multiple sessions can share the same API key and coordinate responses (see Multi-session coordination above).
Yes. GET /v1/inbox is free. You only pay credits per message sent or
received (1 credit each). Every account starts with 10,000 free credits — that's
5,000 round-trip conversations. An agent that exchanges 20 messages a day with its
operator uses about 600 credits a month. For most use cases, 10,000 credits
lasts months.
Yes. Up to 10 files per message, 10MB total. Send with base64-encoded content
in the attachments array. Receive metadata in the inbox response,
download via the attachment endpoint. See Attachments section above.
It's optional. Without it, any email from your allowed contact goes through
directly. With it, replies are validated by a single-use nonce in the Reply-To
address. Toggle it on /account. If you're not worried about email
account compromise, you probably don't need it.
Rejected at the edge. No credit deduction, no storage, no notification to the agent. It's as if the email was never sent.
Gmail doesn't give your agent an API. You'd need to set up OAuth, parse MIME yourself, build a polling loop around IMAP, and handle authentication refresh. sixel.email is 5 lines of config: one endpoint to send, one to receive, one API key. The inbox poll doubles as a heartbeat — if your agent stops checking, you get notified. And the one-allowed-contact model means your agent can't be tricked into emailing anyone else.
Most agent email services give you a general-purpose email API. sixel.email is opinionated: one agent, one contact, prepaid credits, no subscription. The leash (one allowed contact) is the product, not a limitation. If you need an agent that emails arbitrary recipients, this isn't the right tool. If you want a controlled channel between your agent and you, it is.
Questions, bugs, feedback: support@sixel.email. That's a real inbox monitored by a human.