For agents · Published 2026-09-18 · Updated 2026-10-27 · AgentiSend
Email API for AI agents: what to require and 7 options compared
An AI agent should send email through an API that caps it, detects its loops, can be stopped by a human and explains its own refusals. This page names the endpoint for each of those.
An AI agent should use an email API that gives every key a budget it cannot raise, detects when the agent starts repeating itself, lets a human stop all sending with one request, holds risky sends for a person instead of dropping them, publishes the account's standing in JSON, and returns a fix field on every refusal. This page names the exact endpoint for each of those, with the request that calls it.
Base URL: https://api.agentisend.com. Every request carries Authorization: Bearer as_…. Every response carries ratelimit-limit, ratelimit-remaining and ratelimit-reset.
If you want the six steps from no account to a delivered email instead, read /agents. The one-sentence definition of this product is Transactional email API for AI agents.
What to require
Each requirement below is one H2. The table under it is the same requirement across seven options, verified 2026-09-28 from the linked comparison. AgentiSend's free plan is 5,000 emails a month, 170 a day, and 3 domains.
| Option | Per-key budget | One-call pause | Idempotency | Inbound mail |
|---|---|---|---|---|
| AgentiSend | On every key | POST /limits/kill-all | 7 days, every mutating call | Every verified domain, one MX record |
| Resend | Not on the pricing page | Not on the pages fetched | 24 hours, two endpoints | Every plan |
| Amazon SES | Not on the pricing page | One call sets sending to off | Not on the SendEmail page | Priced per 1,000 |
| Postmark | Not on the pricing page | Not on the pages fetched | Not supported | Pro and Platform |
| Mailgun | Not on the pricing page | Not on the pages fetched | Not on the pages fetched | Routes, including Free |
| SendGrid | Not on the pricing page | Not on the pages fetched | Not on the mail-send page | Not on the pages fetched |
| AgentMail | Not on the pricing page | Not on the pages fetched | 24 hours on send | Inboxes are the product |
Loops and Plunk are compared on their own pages: /vs/loops, /vs/plunk.
Per-key budgets
A budget belongs to an API key, not to the account, and it is checked before the send rather than reported after it. Give each agent its own key so one agent's ceiling is not another's.
curl -sS -X PATCH https://api.agentisend.com/limits/keys/9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42 \
-H "Authorization: Bearer $AGENTISEND_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"budget_per_period":500,"rate_ceiling_per_minute":20}'200
{
"api_key_id": "9c8f8f0e-3d1a-4d3f-9a1e-2b7c1a0f5e42",
"budget_per_period": -9007199254740991,
"consumed_in_period": -9007199254740991,
"consumed_in_window": -9007199254740991,
"paused": false,
"paused_at": "2026-09-04T09:14:00.000Z",
"paused_reason": "example",
"period": "hourly",
"period_started_at": "2026-09-04T09:14:00.000Z",
"rate_ceiling_per_minute": -9007199254740991,
"rate_window_started_at": "2026-09-04T09:14:00.000Z",
"updated_at": "2026-09-04T09:14:00.000Z"
}Every field in the body is optional, and you send the ones you want to change:
{ "budget_per_period": 500, "rate_ceiling_per_minute": 20 }period is hourly, daily or monthly. budget_per_period is a count of sends; rate_ceiling_per_minute is a separate per-minute ceiling. An API key can only lower its limits, and a shorter period is compared over 30 days: 500 a day is up to 15,000, far more than a new Free key's 5,000 a month, so a change like that is made by a person in the console. Read the current state of one key with GET /limits/keys/{id}, or the whole fleet with GET /limits/keys.
When the budget is spent the send is refused with agent_budget_exceeded. Nothing is queued behind the refusal, nothing is charged, and no request the agent can make raises its own ceiling. When the account's plan inclusion is spent instead, the code is plan_limit_reached.
Longer version: /agent-email-budgets. The category page that lines the usual DIY checklist up against these calls is Email API with spending limits for AI agents.
Loop detection
The guard reads this key's sends from the last 60 minutes before every send. It refuses a candidate that would be the 4th near-identical message to the same recipient. On a key set to full, and on a marketing send from a key with no setting, it also refuses the next send to an inbox that a window of at least 20 sends has collapsed onto, once that window has fewer than 3 effective recipients. Mail to anyone else still sends. A new key is repetition-only. whoami reports loop_guard.
A refused send answers approval_required with the evidence attached, and the held message is recorded in the approval queue rather than discarded.
{
"error": {
"code": "approval_required",
"message": "Blocked: this agent has sent 3 near-identical emails to customer@example.com within 60 minutes, which matches a retry or script loop; review the agent before sending more.",
"fix": "Do not send it again: a person approves or rejects it in the console under Agents → Approvals, and approving sends it — the message then appears in GET /emails. action_id in this error names the held send; the key that asked cannot approve itself, and a retry waits on the same approval.",
"docs_url": "https://agentisend.com/what-does-approval-required-mean#approval_required",
"retryable": false
}
}GET /limits/keys returns loop_state per key, one of clear, watch or tripped, with the evidence behind it.
Longer version: /agent-email-loop-detection. When a loop is already running, pause the key and read the holds on Stop an AI agent sending emails in a loop.
The kill switch
One request pauses every key on the account:
curl -sS -X POST https://api.agentisend.com/limits/kill-all \
-H "Authorization: Bearer $AGENTISEND_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{}'200
{
"affected_key_ids": [],
"at": "2026-09-04T09:14:00.000Z",
"paused": false,
"reason": "example"
}The reply lists affected_key_ids and the time it took effect. Sends from a paused key are refused with kill_switch_active. Nothing auto-resumes: POST /limits/resume-all brings the account back, POST /limits/keys/{id}/resume brings one key back, and budgets and ceilings are unchanged by either. One key can be stopped on its own with POST /limits/keys/{id}/kill.
If you are the agent and your sends start answering kill_switch_active, a person did that on purpose. Report it; do not route around it.
Approval queues
A held send is a row, not a loss. List what is waiting:
curl -sS -X GET https://api.agentisend.com/agent-actions \
-H "Authorization: Bearer $AGENTISEND_API_KEY"200
{
"data": [],
"has_more": false,
"next_cursor": "example",
"object": "list"
}Each row carries state (pending, approved, rejected or killed) and a preview with the subject, to_count, the first three recipients, the key that asked and held_reason. A human approves with POST /agent-actions/{id}/approve, which executes the held send through the same path a normal send takes and returns the message_id, or rejects with POST /agent-actions/{id}/reject and a reason that is kept with the row.
An agent that reads approval_required as a failure and retries fills the queue. Poll the action instead.
Longer version: /agent-email-approvals.
Machine-readable standing
Check standing before a large send instead of discovering it mid-run:
curl -sS -X GET https://api.agentisend.com/trust/standing \
-H "Authorization: Bearer $AGENTISEND_API_KEY"200
{
"domains": [],
"history": [],
"next_review_at": "2026-09-04T09:14:00.000Z",
"probation": false,
"probation_cap": -9007199254740991,
"probation_ends_at": "2026-09-04T09:14:00.000Z",
"reason_codes": [],
"reasons": [],
"sla_deadline_at": "2026-09-04T09:14:00.000Z",
"state": "ok",
"streams": {
"marketing": {
"bounce_rate": 1,
"bounced": -9007199254740991,
"complained": -9007199254740991,
"complaint_rate": 1,
"sent": -9007199254740991,
"state": "ok"
},
"transactional": {
"bounce_rate": 1,
"bounced": -9007199254740991,
"complained": -9007199254740991,
"complaint_rate": 1,
"sent": -9007199254740991,
"state": "ok"
}
},
"window_days": -9007199254740991
}state is ok, warning, throttled or paused. reasons carries the metric (bounce or complaint), the measured value, the threshold it is judged against, the window, and sample messages. The thresholds themselves are published at GET /trust/thresholds: bounce rate warns at 2% and pauses at 4%, complaint rate warns at 0.05% and pauses at 0.08%, and no step is taken until 100 delivered-or-bounced sends in the window. Complaint counts come from mailbox providers that send us feedback reports; Gmail, Yahoo and Outlook do not send them to us today, so a complaint rate of 0 is not evidence that nobody complained.
If state is not ok, fixing the list is the move, not sending more.
The fix field
Every 4xx and 5xx carries code, message, fix, docs_url and retryable. fix is an instruction that names the endpoint to call next, so an agent has something to do other than retry. retry_after_seconds is present exactly when retryable is true.
{
"error": {
"code": "domain_not_verified",
"message": "This sending domain is not verified.",
"fix": "If it is pending, publish the required records from GET /domains/:id; checks continue for 72 hours. Test now by sending to an address ending in @simulator.agentisend.com. If it is not registered, POST /domains first. If it is failed, GET /domains/:id names the record to fix, then POST /domains/:id/verify to reopen the window.",
"docs_url": "https://agentisend.com/docs/errors#domain_not_verified",
"retryable": false
}
}All 122 codes are at /docs/errors, and as Markdown at /docs/errors.md.
To find out whether a send would pass every gate without sending it, call POST /emails/preflight. It runs the domain, sandbox, trust, suppression, budget, loop and content checks through the same code path POST /emails uses.
MCP
The same API, same limits, same error contract, over one streamable HTTP endpoint with bearer auth:
https://api.agentisend.com/mcpThe tools that matter to the control plane are get_agent_budget, set_limit, get_kill_switch_state, list_pending_approvals, request_approval, get_trust_standing, preflight_email and why_was_this_not_sent. Every tool that changes something accepts idempotency_key. The full catalogue with the scope each tool needs is at /docs/guides/mcp; client configuration is at /docs/guides/agents.
There is no looser path in over MCP. An agent gets the same budget, the same loop guard and the same kill switch it gets over REST.
Idempotency
Send Idempotency-Key on every call that changes something. Repeating the call with the same key replays the first result instead of doing the work twice, which is what makes a crashed agent safe to restart. Keys are remembered for 7 days.
Two codes exist so a retry is never ambiguous: idempotency_in_flight when the first request is still running, and idempotency_payload_mismatch when the same key arrives with a different body.
What this service will not do
No cold outreach. There is no lead database, no scraping, and no bulk import of addresses that never asked to hear from you. A request that looks like unsolicited sending is refused, and asking differently will not change the answer.
Send from your client
One page per client. The title is the question. Editors and coding agents that speak MCP get the config on the page. App builders get a prompt and a server-side sample, because none of them has a connector.
Whether you have to build those guardrails yourself: /agent-email-guardrails.
Questions
The title is the question. The first sentence is the answer.
- Why was my email delayed?
- Why did my email bounce?
- What does domain_not_verified mean?
- How do I verify a sending domain on Cloudflare?
- How do I verify a sending domain on Namecheap?
- How do I verify a sending domain on GoDaddy?
- How do I verify a sending domain on Route 53?
- How do I verify a sending domain on Google Domains?
- How do I verify a sending domain on Porkbun?
- How do I verify a sending domain on Hostinger?
- How do I send email from a Cloudflare Worker?
- How do I send email from AWS Lambda?
- How do I stop an AI agent from sending too many emails?
- Does AgentiSend have a free plan?
- How do I test email sending without delivering anything?
- How do I put a spending limit on an AI agent that sends email?
- How do I require human approval before an AI agent sends email?
- How do I stop prompt injection in email my agent reads?
- How do I see everything my AI agent sent?
- How do I pause an AI agent that is sending email?
- How do I stop my code from sending the same email twice?
- How do I schedule an email to send later?
- How do I cancel a scheduled email?
- How do I send many emails in one request?
- How do I send an email with an attachment?
- How do I set reply-to, cc and bcc on an email?
- How do I send an email from a template?
- How do I tag emails and find them later?
- How do I know if my email was delivered?
- How do I handle rate limits?
- How do I verify a webhook signature?
- Why did my webhook stop receiving events?
- How do I replay webhook events?
- How do I remove an address from the suppression list?
- How do I add an unsubscribe link to my emails?
- Why are my emails going to spam?
- Why does my email say delivered but the recipient did not get it?
- Should I send email from a subdomain or the root domain?
- What does account_sandboxed mean?
- How do I set up DMARC for my sending domain?
- Can I have two SPF records on one domain?
- How do I export my email data?
- How do I rotate an API key without downtime?
- How long does AgentiSend keep my emails?
- Can I keep my email data in the EU?
- How do I send email from Node.js?
- Which API key permission should an AI agent have?
- How do I restrict an API key to one domain?
- How many recipients can one email have?
- How do I revoke an AI assistant connection?
- How do I find out why an email was not sent?
- How do I check my sending reputation?
- What does partially_verified mean for a domain?
- Why did my domain verification fail?
- How long does domain verification take?
- How do I wait for a domain to verify from a script?
- What is the return-path record for?
- What does a spam complaint do to my account?
- How do I get notified when domain verification breaks?
- What does approval_required mean?
- What does kill_switch_active mean?
- What does agent_budget_exceeded mean?
- What does rate_limit_exceeded mean?
- What does suppressed_recipient mean?
- Can an AI agent sign up for AgentiSend by itself?
- What does plan_limit_reached mean?
- What does restricted_api_key mean?
- What does idempotency_payload_mismatch mean?
- How do I invite a teammate and set their role?
- How do I control account notifications?
- How do I close my account?
- How do I limit who can email my AI agent?
- What happens when I hit my plan limit?
- How do I see my usage?
- How do I send an email when a custom event happens?
- How do I create a segment of contacts?
- How do I receive email at my own domain?
- How do I read a reply without the quoted history?
- How do I get the text of an email attachment?
- How do I hold suspicious email until a person reviews it?
- What does inbound_verdict review mean?
- How do I reply to an email my agent received?
- How does an AI agent read a one-time code from email?
- How do I give each AI agent its own email address?
- How do I hand DNS setup to the person who owns the domain?
- What does agent_unconfirmed mean?
- What does recipient_ramp mean?
- How do I block a sender from emailing my agent?
Jobs
Seven jobs, each with the ceiling for that key, a snippet the test suite runs, the refusal that job hits, and one limit:
- Support agent replies
- Receipts from a store agent
- Sign-in codes
- Agent daily digest
- Alerts from a monitoring agent
- Approval-gated announcements to existing customers
- Onboarding sequence run by an agent
The index is Use cases.
Where everything else is
- /agents — no account to delivered email, in six requests.
- /llms.txt — the index: figures, plans, every operation, error and event.
- /openapi.json — the document these pages are generated from.
- /docs/guides/budgets-and-the-kill-switch — the guide.
- /vs/resend — the same control plane, compared row by row.