For developers
Full-duplex JMAP under the hood. Policy enforced server-side, before the LLM sees anything. ~40 lines to wire up.
Building a prototype? Engineers can spin up a free sandbox to exercise the policy gate and JMAP API end-to-end.
export const policy: MailPolicy = {
defaultAction: "bounce",
senders: [
{
match: { address: "[email protected]", requireDkim: true, requireSpf: true },
capabilities: ["read_calendar", "propose_meeting", "confirm_meeting"],
rateLimit: { perHour: 20, perDay: 100 },
tokenBudget: { perThread: 20_000 },
},
{
match: { address: "[email protected]", requireDkim: true },
capabilities: ["ingest_conflict_notice"],
rateLimit: { perHour: 200 },
},
],
contentGuards: [
{ reject: /ignore (previous|all) instructions/i, reason: "prompt_injection_suspected" },
],
auditLog: { retentionDays: 90, includeBodyHash: true },
};Every inbound message is evaluated against this document before your agent runs. There is no "just talk to the LLM" path.
Only messages from [email protected] (with DKIM and SPF passing) reach the agent. Everything else bounces.
The boss can read the calendar and book meetings. The conflict-bot can only ingest conflict notices. The agent cannot do anything outside the listed capabilities.
Bodies matching a known prompt-injection regex are rejected before the LLM sees them. Audit row records the reason.
Every decision — accepted, rate-limited, content-guarded — is logged for 90 days, with a SHA-256 of the inbound body for proof.
Full schema in the policy reference.
The question isn't who ships more features — it's whether you're optimising for agent autonomy and scale, or for human control and auditability. Most tools here cover one half of the email problem; Mailbuttons covers both halves and adds the policy layer that agents in regulated settings specifically need.
| Mailbuttons | Self-hosted (Postfix/Stalwart) | Cloudflare Email Workers | Resend / Postmark / SendGrid | AgentMail (and similar) | |
|---|---|---|---|---|---|
| Inbound + outbound in one product | ✓ | ✓ (you build it) | Inbound only, raw | Outbound only | ✓ |
| DKIM/SPF verification on inbound | Built-in | You implement | You implement | N/A | Varies |
| Per-sender capability scoping | Built-in | — | — | — | Not a focus |
| Pre-LLM content guards | Built-in | You implement | You implement | — | Not a focus |
| Tamper-evident audit log of agent decisions | Built-in | You implement | — | — | Not a focus |
| Human-granted provisioning (no agent self-signup) | Built-in | You build | — | — | Not a focus — agents self-provision |
| EU/UK data residency | Built-in | Your choice | — | Varies | Not a focus |
| Self-serve provisioning at scale | Human-granted | You build | Limited | Outbound only | Built for it |
| Time to first agent | Minutes | Days–weeks | Hours | Hours (outbound only) | Minutes |
To be fair to the agent-email APIs: if you want agents to spin up their own inboxes at scale and move fast, tools like AgentMail are built for exactly that, with strong deliverability and frictionless self-serve onboarding. If a human needs to authorise what each agent can do, and you need an auditable record for a regulator, that's us.
Stalwart receives over SMTP. JMAP normalises it. DKIM/SPF/DMARC verified.
Sender match → verification → content guards → rate limit → token budget. Audit row written.
Webhook fires with the verified message and granted capabilities. Your code replies via the SDK.
Three commands to a working agent. Same code as the reference integration READMEs.
# Run the reference integration
git clone https://github.com/mailbuttons/claude-scheduling-agent-ts
cd claude-scheduling-agent-ts && npm install
MAILBUTTONS_API_KEY=sk_... ANTHROPIC_API_KEY=sk-ant_... npm startSee also: 60-second walkthrough.
What the policy layer actually defends against.
| Threat | Control |
|---|---|
| Random sender on the internet talks to your LLM | Sender allowlist + DKIM/SPF requirement |
| Spoofed From: header | DKIM/SPF/DMARC verdicts surfaced and required |
| Prompt injection via message body | Server-side regex content guards, before tokenisation |
| LLM cost runaway from one prolific sender | Per-sender per-thread and per-day token budgets |
Honest list. Subscribe / star the GitHub org if any of these matter to you.
Mailbuttons runs a managed, multi-tenant email API with a JMAP-compatible control plane and agent-focused extensions.
Authenticate with your tenant key and forward JMAP method calls exactly as you would against any standards-compliant server. Mailbuttons preserves compatibility while layering optional AI memory features through `_mailbuttons` hints.
List messages in an agent inbox:
# Save the payload once for reuse
cat <<'EOF' >/tmp/email-query.json
{
"using": [
"urn:ietf:params:jmap:core",
"urn:ietf:params:jmap:mail"
],
"_mailbuttons": { "account": "[email protected]" },
"methodCalls": [
[
"Email/query",
{
"accountId": "{{accountId}}",
"filter": { "inMailbox": "{{mailboxId}}" },
"sort": [{ "property": "receivedAt", "isAscending": false }],
"limit": 25
},
"c1"
]
]
}
EOF
# Fetch the tenant-scoped JMAP session
curl -s https://emailapi.mailbuttons.com/api/v1/jmap/session \
-H "Authorization: Bearer <TENANT_API_KEY>"
# Issue a JMAP request (email query in this example)
curl -s https://emailapi.mailbuttons.com/api/v1/jmap \
-H "Authorization: Bearer <TENANT_API_KEY>" \
-H "Content-Type: application/json" \
--data @/tmp/email-query.jsonTip: The Rust and Python SDKs wrap this payload alongside type-safe helpers for common operations like queueing outbound mail or ingesting threads.
POST /api/domains/addPOST /api/domains/{domain}/verify