Sendmux

A real inbox.
Parsed for your agent.

Every mailbox is a real address on a real domain. Read inbound mail as clean JSON, stream new messages over SSE, retrieve over IMAP, or POST signed webhooks. One credential covers every surface.

Start instantly on @myagent.mx · custom domain in minutes · IMAP, SMTP, REST and SSE share one credential

message · msg_q3v9pf8z body_mode=clean_json
01  Raw9,400 ch
Nora · Ops cluster
Re: invoice 4187
Got it, paid on Tue, thanks.
— Nora · Operations · m: +61 412 555 901 · ops-cluster.dev
> Hi Nora, can you settle invoice 4187 by EOD?
> Cheers, Billing bot
02  Strip−noise
×tracking pixel
×signature block
×quoted history
body text
attachment list
03  Extract+typed
linksops-cluster.dev/inv/4187
filesinvoice.pdf · 18 KB
threadthr_7a2k
spamfalse
sendernora@ops-cluster.dev
04  Clean JSON28 ch
{
  "subject": "Re: invoice 4187",
  "body": {
    "text": "Got it, paid Tue, thanks.",
    "signature_stripped": true,
    "quotes_stripped": true
  },
  "attachments": []
} 200 OK · ready for the agent
noise signal9,400 chars28 chars
NoraRe: invoice 41872 min
BookingBooking confirmed4 min
PaymentsRefund · A$84.2011 min

The differentiator

What humans see, what your agent reads.

Sendmux strips signatures and quoted history by default, extracts links into a typed array, and returns truncation metadata. Every toggle is exposed if you want different behaviour.

Human view · rendered HTML

Nora, Ops cluster <nora@ops-cluster.dev>

to support-agent@mail.ops-cluster.dev

Subject: Re: invoice 4187

Got it, paid on Tue, thanks.

[tracking pixel · 1×1 gif]

,
Nora · Operations lead · Ops cluster
m: +61 412 555 901
e: nora@ops-cluster.dev
w: ops-cluster.dev

On Tue, 14 May 2026 at 09:12 Billing bot wrote:
> Hi Nora, can you settle invoice 4187 by EOD?
> Cheers, Billing bot

~9,400 chars · noise dominates the signal

Agent view · clean JSON

{
  "id": "msg_q3v9…",
  "subject": "Re: invoice 4187",
  "body": {
    "text": "Got it, paid on Tue, thanks.",
    "signature_stripped": true,
    "quotes_stripped": true,
    "extracted_links": []
  },
  "attachments": []
}

28 chars of signal · 0 chars of noise

Cleaning controls

Pick what to strip. Pick what to keep.

Same controls on single messages, threads, and batches up to 100. Pass body_mode=clean_json to any content endpoint. Override any default in one query parameter — toggle them below to see the request and the response shift in place.

GET  /mailbox/messages/{id}/content
strip_signatureRemove the trailing sign-off block.
strip_quotesRemove quoted history from prior replies.
include_linksExtract URLs into a typed array.
include_htmlKeep the rendered HTML alongside the text.
include_headersHow many headers to expose to the agent.
max_body_charsBody cap. Tunable up to one million.
GET /mailbox/messages/msg_q3v9/content?body_mode=clean_json&strip_signature=true&strip_quotes=true&include_links=true&include_html=false&include_headers=selected&max_body_chars=100000
response · clean_json 200 OK
{
  "id": "msg_q3v9pf8z",
  "thread_id": "thr_7a2k",
  "subject": "Re: invoice 4187",
  "body": {
    "format": "text",
    "text": "Got it, paid Tue, thanks.",
    "signature_stripped": true,
    "quotes_stripped": true,
    "truncated": false,
    "max_chars": 100000,
    "extracted_links": [
      "ops-cluster.dev/inv/4187"
    ]
  },
  "headers": { "From": "…", "Date": "…" },
  "attachments": [
    { "filename": "invoice.pdf", "size_bytes": 18422 }
  ]
}

Conversations

Threads, grouped by Message-ID.

Every message lands in a thread. Sendmux groups them automatically by Message-ID and References headers — RFC 2822, handled for you. List threads with filters, fetch one for participants and message IDs, or stream the cleaned content.

thread · thr_7a2k3 msgs · 1 unread
01
Billing bot
<A1-billing-4187@ops-cluster.dev>
Hi Nora, can you settle invoice 4187 by EOD?
Tue · 09:12
02
Nora · Ops cluster
<B2-reply.q3v9@ops-cluster.dev>
Sent the payment, will confirm Wednesday.
References <A1-billing-4187>Tue · 14:48
03
Nora · Ops cluster
<C3-reply.k8mn@ops-cluster.dev>
Got it, paid Tue, thanks. · unread
References <A1…> <B2-reply.q3v9>Wed · 09:02
GET /mailbox/threadsunread=trueparticipant=…after=2026-05-14

Auto-grouped, no bookkeeping

Replies and forwards attach to the right thread by header — you never reconcile Message-IDs yourself.

List with filters

Page threads by unread, participant, or date window. Each item carries participants and ordered message IDs.

Fetch cleaned, in order

Pull a single thread and stream each message as clean_json, oldest to newest — ready to feed straight to context.

0
Messages per batch fetch
/messages/batch
0MB
Inbound & outbound attachment
per message
0M
Max body characters, tunable
max_body_chars
0
Access surfaces, one credential
REST · SSE · IMAP · SMTP

Read patterns

Sync on your schedule, or stream in real time.

Poll a typed change feed when a loop suits you, or hold an SSE connection open and react the moment mail lands. Same cleaned payloads either way.

Typed change feed

Call /mailbox/changes with a cursor. You get back exactly what moved — new messages, reads, deletions — and a fresh cursor to resume from.

Cursor-based — never miss or replay an event
Idempotent — safe to retry a page
Runs anywhere a cron job runs
GET /mailbox/changes
# resume from your last cursor
GET /mailbox/changes?cursor=crs_8f3a

{
  "changes": [
    { "type": "message.created", "id": "msg_q3v9" },
    { "type": "message.read", "id": "msg_p1aa" }
  ],
  "next_cursor": "crs_9b21",
  "has_more": false
}

Open SSE connection

Hold /mailbox/stream open and events push as they happen. Auto-reconnect with Last-Event-ID means no gap across a dropped link.

Sub-second delivery on arrival
Resumes from Last-Event-ID after a drop
Plain SSE — no SDK, no websocket dance
GET /mailbox/stream connected

Server-to-server

Signed webhooks you can actually trust.

Every webhook is signed with an HMAC over the raw body. Verify in constant time, check the timestamp, and you know it came from Sendmux — not a spoofer.

1

Raw-body signature

Sign the exact bytes you received — never the re-serialised JSON.

2

Timestamp window

Reject anything older than five minutes to kill replays.

3

Constant-time compare

Use hmac.compare_digest — no timing leaks on the secret.

verify.py awaiting POST
# POST from Sendmux → your endpointsig = request.headers["X-Sendmux-Signature"]ts = request.headers["X-Sendmux-Timestamp"]# recompute over the RAW bodyexpected = hmac.new(secret, f"{ts}.{raw}", sha256)fresh = now() - int(ts) < 300 # 5 minok = fresh and hmac.compare_digest(sig, expected) → verified · event=message.created · id=msg_q3v9 return 200 OK

Attachments

25 MB in. 25 MB out. No surprises.

Inbound attachments arrive parsed with filename, type, and size. Small files come through inline; large files get a presigned URL so you stream them on your terms.

per-message ceiling25MB
body + inline ≤ 10MB
presigned ≤ 25MB
010MB25MB
≤ 10MB · inline

Small files, base64 inline

Tiny PDFs, receipts, and CSVs ride along in the message payload. One fetch, nothing extra to wire up.

"attachments": [{
  "filename": "invoice.pdf",
  "size_bytes": 18422,
  "content": "JVBERi0xLj…"
}]
≤ 25MB · presigned

Large files, presigned URL

Anything bigger returns a short-lived download URL. Stream it straight to storage — it never bloats your message payload.

"attachments": [{
  "filename": "deck.pptx",
  "size_bytes": 22118400,
  "download_url": "https://…",
  "expires_in": 3600
}]

Where mail lives

Your domain or ours. You pick.

Custom domain provisioning is atomic — partial failure rolls back, so a retry never leaves you half-configured. Sendmux re-verifies every six hours and emails owners the moment a record stops resolving.

mail.ops-cluster.devchecking…
MX10 · mail.sendmux.aicheck
TXT · own_sendmux → sendmux-verify=team_clz…check
TXT · SPFv=spf1 include:amazonses.com ~allcheck
TXT · DMARCv=DMARC1; p=quarantinecheck
CNAME · DKIM ×3token{1,2,3}._domainkey → ….dkim.amazonses.comcheck
Re-verifyEvery 6 hours. Owners emailed on persistent failure.Auto

No DNS, no setup

Skip the lot with @myagent.mx

Claim a local-part under our shared domain at signup. Globally unique, and outbound rides the managed Amazon SES route automatically.

support-agent@myagent.mx
  • Ten seconds to a working mailbox
  • Move to your own domain whenever
  • Same Mailbox API — REST, SSE, IMAP, SMTP

Operate

Sender rules in front. Suspend, resume, empty behind.

Sender rules run before a message is accepted — three modes: off, allowlist, denylist. Each rule is an allow or deny pattern with an optional note.

Mail rejected by sender rules is not billed — which makes allowlists the cheapest cost-control lever in the system. It runs before any agent code or webhook fires.

Sender rules · mode: allowlist3 rules · active
Allow*@ops-cluster.devInternal team
Allow*@booking-engine.devBooking platform
Allow*@payment-events.devPayment events
All other senders rejectednot billed
Suspend ⇄ Resume

Suspend

Cuts authenticate, send, and receive in one call. Resume reverses it.

Empty

Empty

Destroys every message in the mailbox. The mailbox itself stays.

Delete

Delete

Removes the mailbox and its stored mail together. One atomic step.

One credential

Four surfaces. One password.

01

REST API

app.sendmux.ai/api/v1/mailbox/*

Read, search, send, sync. 35+ endpoints.

02

SSE stream

GET/mailbox/events

Live message.received feed. No polling.

03

IMAP

mail.sendmux.ai:993TLS

Plug into anything that speaks IMAP.

04

SMTP

smtp.sendmux.ai:587STARTTLS

Send from the same mailbox address.

one credential · switch surface, never re-onboard

Questions

Answered plainly.

Yes. Call GET /mailbox/messages/{id}/content or batch up to 100 via messages:batch-get with body_mode=clean_json. Sendmux strips signatures and quoted history by default, extracts links into a typed array, and truncates at 100,000 characters per message. Every toggle is exposed if you want different behaviour.
Yes. GET /mailbox/events is a Server-Sent Events stream of message.received and message.received.spam. Pass Last-Event-ID to resume after a disconnect, set ping between 10 and 300 seconds for heartbeats, or close_after between 30 and 3600 seconds for a bounded session.
Yes. The same mailbox credential is your IMAP password at mail.sendmux.ai:993 using implicit TLS. One credential covers the HTTP Mailbox API, IMAP retrieval, and SMTP submission. Pick whichever protocol your agent framework already supports.
No. Claim a local-part under @myagent.mx on signup and you have a working mailbox in seconds, with full REST, SSE, IMAP, and SMTP access. When you are ready for branded sending, add a custom domain. Seven DNS records and a one-click verify.
Accepted inbound mail is billed at $0.15 per 1,000 emails, matching standard outgoing. Mail rejected by your mailbox sender rules is not billed, so allowlists and denylists double as a cost-control lever. No per-mailbox fees and no per-seat fees.
Webhooks are signed POSTs to your endpoint. Pick them when your service should receive events even while clients are offline. The Server-Sent Events stream is a live HTTP connection an agent holds open. Pick it when your code reacts the moment a message arrives.

EMAIL INFRASTRUCTURE FOR AI AGENTS

Your agents need email. Start sending in five minutes.

One API for outbound routing, inbound mailboxes, and everything in between. Free to start, scales to millions.