Transactional Email API: How It Works and What to Check

Your agent has its own address now. It sent a receipt, the API returned 200, and everything looked fine until the customer replied asking why they'd received the same receipt twice. A second thread shows a password reset the user never got. Both sends returned the same status code.
That's the gap worth closing first. A send call proves the request was accepted. It says nothing about whether the message reached a mailbox, whether a retry created a duplicate, or whether an inbound message is trying to give your agent new instructions. Treat the transactional email API as an event system with failure states, and the rest of the design follows.
Table of Contents
- What a Transactional Email API Replaces
- How a Send Request Moves Through Delivery States
- How to Send Your First Email Through an API
- Bounce and Complaint Thresholds to Monitor
- Keeping Instructions Hidden in Email Out of Your Agent
- Stopping Duplicate and Conflicting Sends Across Agents
- Choosing Your Sending Setup Before Go-Live
What a Transactional Email API Replaces
A password reset that never arrives is the most expensive email in your system. The user can't sign in, support gets a ticket, and your application log shows one successful HTTP call with no error attached. Nothing in that log tells you whether the recipient's server rejected the message, throttled it, or filtered it into spam.
A transactional email API is an HTTP endpoint your application calls when one thing happens: an account is created, a password is reset, an invoice is issued, an alert fires, a support reply is posted. One message, one recipient, one trigger. That's what separates it from a bulk marketing campaign, where a single request fans out to a list and every message shares a template and a schedule.
The work it removes is the part nobody wants to own. Amazon SES exposes delivery, bounce, complaint and reject events through its API, plus account-level statistics through the GetSendStatistics operation, so each send produces machine-readable outcomes instead of a shrug. That model, not the act of transmitting mail, is what's worth adopting.
| Task | Running SMTP yourself | Transactional email API |
|---|---|---|
| Connection and login | You configure the SMTP host, port, TLS and credentials | You authenticate with an API key in a request header |
| Retries after a timeout | Your code queues and retries each failed attempt | The provider queues and retries, and you read the final state |
| Bounce and complaint handling | You parse bounce messages and feedback reports yourself | Delivery and bounce events arrive as machine-readable records |
| Delivery reporting | You build your own logs of what was accepted | Each message has an ID and a status that changes over time |
Three outcomes get blurred in most discussions. Accepted means the provider took the message. Delivered means the recipient's mail server accepted it. Inbox placement is a separate outcome shaped by sender reputation, authentication, content, recipient engagement and complaint behavior, and no API can promise it.
What the API can't guarantee: delivered means the receiving server said yes. It doesn't mean a person saw it.
Users do go looking when a transactional message goes missing. In 2024, 71% of consumers said they would check their spam folder when a transactional email failed to arrive in their inbox, according to Sinch Mailgun's consumer research. That's one reason delivery state matters more than delivery optimism.
Keep five fields for every send: your internal event ID, the provider message ID, the recipient, the template name and a timestamp. When a customer asks what happened to their reset, that row answers the question in one lookup instead of a log dig. If you're still weighing providers, Rankozo's comparison of affordable transactional email providers puts plans and limits side by side before you commit.
How a Send Request Moves Through Delivery States
The HTTP response to a send request is an acknowledgement, not a delivery receipt. RFC 5321 treats delivery as a stateful conversation between servers, with independent configurable timeouts for each command and data buffer, a recommended initial server-response and server-idle timeout of five minutes, and a retry interval of at least 30 minutes to the same destination after a failed attempt. An API that hides all of that behind a 202 still has to model it somewhere, and that state should be visible to your code.

| Status | What it means | What your code should do |
|---|---|---|
| Queued | Accepted and waiting to be sent | Store the message ID and wait |
| Scheduled | Set to send at a later time | Show the planned send time to users |
| Pending approval | Held until a person or policy releases it | Notify the approver and do not send a second copy |
| Accepted | The provider took the message for delivery | Keep waiting for a final event |
| Sent | Handed to the recipient's mail server | Keep waiting for delivered or bounced |
| Delivered | The recipient's server accepted the message | Mark the notification as complete |
| Deferred | Temporary failure with a retry scheduled | Do nothing, the retries will run |
| Bounced | The recipient's server rejected the message | Suppress the address for future sends |
| Complained | The recipient reported the message as spam | Suppress the address and review the content |
| Failed | Sending stopped without success | Alert a person and check the error |
The path a message takes
The request is accepted and a durable message ID returns immediately. A policy check runs, and any hold or rejection happens there rather than after transmission. The message waits in a queue, gets handed to the recipient's server, and finishes as delivered, bounced, complained or failed. The response you got at the start only ever described step one.
Transient versus permanent failures
Classify before you handle. Connection timeouts, temporary 4xx responses and DNS problems are transient: the message may still land, so retry with exponential backoff, keep a per-recipient cap, and stop after a bounded number of attempts. A permanent 5xx rejection won't change on retry, so suppress the address and move on. Mixing the two is how retry storms start, and a storm is worse than a failure because it keeps damaging sender reputation while it runs.
The rule for application code: store the message ID when you make the request, treat deferred as still in progress, and update state from events. Polling burns rate limit and still arrives late.
How to Send Your First Email Through an API
Seven steps take a developer from an API key to a delivered message with a tested bounce path. Each one has a specific slip that costs an afternoon.

- Create the key and read it from the environment. Put it in a variable your process loads at start, never in client-side code. The usual mistake is a key pasted into a frontend bundle, where it's visible to anyone who opens dev tools and can be used to send mail as your domain.
- Verify the sending domain before the first live send. Get SPF and DKIM in place and confirm they validate. A missing DKIM signature is often the difference between the inbox and the spam folder, and you won't discover it from the API response because the send succeeded either way.
- Build the body from the event, not from a string in the handler. Send
from,to,subject, a text part and an HTML part, or a template name with variables. Include the text part; some clients and most security scanners read it, and a message with HTML only looks thin. - Send the POST with an Idempotency-Key set to your internal event ID. If the request times out and your code retries, the key is what stops the second copy. The slip here is a retry loop with a freshly generated key each attempt, which guarantees the duplicate you were trying to prevent.
- Read the returned message ID and save it next to the event that triggered the email. That's the join between your application and the provider's view of the world.
- Register a webhook for delivered, bounced and complained events. Verify the signature on every payload before you trust it, because the endpoint is public whether you advertise it or not.
- Test both paths. Send to an address you control and watch the status move from queued to delivered. Then send to a deliberately invalid address on a test domain and confirm your bounce handling suppresses it rather than retrying forever.
Here's the request shape once the key is loaded:
curl -X POST "$EMAIL_API_BASE/v1/send" \
-H "Authorization: Bearer $EMAIL_API_KEY" \
-H "Idempotency-Key: $EVENT_ID" \
-H "Content-Type: application/json" \
-d '{"from":"agent@yourdomain.com","to":"user@example.com",
"subject":"Reset your password","template":"password_reset",
"variables":{"reset_url":"https://example.com/r/abc123"}}'
What a healthy first send looks like in the logs
One request, one message ID, one delivered event, no retries, no second message. If two message IDs appear for one event, the idempotency key isn't doing its job and the fix belongs before you scale volume, not after. Walking the same sequence with policy checks switched on is covered in sending your first policy-checked email, which is the version worth reading once an agent rather than a person pushes the button.
Providers differ in small ways at the request level, and TopToolAdvisor's Mailjet review covers one API surface in detail if you're comparing it with what you already use.
Bounce and Complaint Thresholds to Monitor
Four numbers belong on a dashboard before an agent starts sending unattended.
| Metric | Healthy target | Risk threshold | Basis |
|---|---|---|---|
| Bounce rate | Under 0.5% | Above 2% (hard bounces) | 2025 analysis of more than 400 billion emails |
| User-reported spam rate at Gmail | Under 0.1% | 0.3% or higher | Google sender guidance |
| Sending domain authentication | SPF, DKIM and DMARC all pass | Unauthenticated mail can land in spam or be rejected with 5.7.26 | Google sender guidance |
Percentages hide volume, so do the arithmetic. At 1 million messages, a 0.5% bounce rate is 5,000 failed deliveries, and a 0.18% rate is 1,800. A 2025 analysis covering more than 400 billion emails across the ten highest-volume industries reported a top delivery rate of 98.05%, with bounce rates in major industries generally below 0.5%, ranging from 0.01% on tightly managed lists to 0.22% in professional services and 0.18% in retail, per Mailgun's email benchmarks report. The same guidance flags hard-bounce rates above 2% and spam-complaint rates above 0.10% as active deliverability risks.
Google's sender guidance sets the user-reported spam rate below 0.1%, with 0.3% or higher producing more severe delivery consequences. Bulk senders above 0.3% can become ineligible for mitigation, and eligibility returns only after the rate stays below 0.3% for seven consecutive days. Google also recommends SPF, DKIM and DMARC on sending domains and warns that unauthenticated messages may be placed in spam or rejected with a 5.7.26 error, as described in Google's authentication guidance.
These are monitoring thresholds, not limits that every mailbox provider enforces identically. Alert before the line rather than at it: a 0.05% complaint rate is a warning worth reading, and anything approaching 0.3% is an outage in progress.
Enforce the pause, don't document it: when a rolling bounce or complaint rate crosses the threshold set for a mailbox or domain, stop sending from it, page a person, and write the decision to the audit trail. A limit that lives in a wiki isn't a limit.
Suppression logic and bounce classification interact more than most guides admit, and suppression lists and bounce handling for AI senders works through the cases where a soft bounce should never become a permanent block.
Keeping Instructions Hidden in Email Out of Your Agent
Authenticated mail is not safe mail. That's the assumption to drop. SPF, DKIM and DMARC answer a narrow question: which domain sent this message. They say nothing about whether the content is safe for a model to read as context.
OWASP's prompt injection entry names email as external content that can carry instructions an agent mistakes for trusted context. A successful injection can expose conversation history or system prompts, make the agent send messages on the user's behalf, trigger unauthorized API calls, or pivot into other connected agents.

The threat isn't hypothetical. A 2025 study of email agents covered 1,404 agent instances across 14 frameworks, 63 applications, 12 large language models and 20 email services, and every instance was successfully hijacked after an average of 2.03 attempts, according to BRSide's analysis of AI email agent prompt injection.
- Mark bodies and attachments as untrusted data. Put explicit delimiters between the message and your system instruction, and keep them there on every call. A delimiter that appears in one prompt template and not the next is a hole.
- Normalize before the model sees anything. Strip hidden text and invisible Unicode, flatten HTML to plain text, and decide deliberately whether quoted replies and forwarded chains are in scope. Forwards are the usual smuggling route because the payload sits below a wall of legitimate-looking quoted content.
- Screen for injection patterns and tool requests. Content that asks the model to call a tool, reveal prior context, or disregard earlier instructions gets flagged before generation rather than after a bad reply.
- Quarantine high-risk originals. Keep the message out of the normal read path until a person reviews it, and preserve the original so the reviewer has something to inspect.
- Check outgoing drafts. Screen for hidden context leaking into a reply and for actions outside mailbox policy, and log the decision either way so a pattern shows up before it becomes a habit.
Authentication is an input to these controls, not a replacement for them.
Digital Souls Studios' writing on AI agent security covers the application side of the same problem, including the capabilities an agent shouldn't be handed in the first place.
Stopping Duplicate and Conflicting Sends Across Agents
A retry is not a duplicate, and confusing the two produces the failure agents create most often. A retry repeats the same send with the same event ID while no final state exists, and should come back with the original message ID. A duplicate is a new request for the same recipient and similar content, and has to be blocked before it leaves the mailbox.
Single-application systems dodge this by accident. Multi-agent systems don't, because two workers can act on the same contact minutes apart and neither knows the other exists.
- Check the last send time against a cooldown window per recipient before writing anything.
- Check the suppression list, which holds addresses that bounced, complained or opted out. A suppression list that lives in one worker's memory is not a suppression list.
- Apply a hard send cap per mailbox and per recipient, so a runaway loop hits a wall instead of your reputation.
- Take a contact lease before writing. A lease makes the second agent wait rather than send, which is the only version of this that holds under concurrency.
- Log the decision, including the blocked sends. A block you can't audit is indistinguishable from a bug.
Set autonomy per mailbox
The NIST AI Risk Management Framework says human roles and responsibilities must be clearly defined for human-AI configurations, which range from fully autonomous operation to fully manual decision-making, and notes some systems require human oversight while others don't. Applied to email, that means a setting per mailbox: automatic sending, approval for first contact, or approval for every message. Molted Email starts new mailboxes on autonomous sending, and approvals are an optional setting rather than the default.
Define the handoff path before you need it. When a thread passes to a person, the agent shouldn't be able to email that contact until the thread comes back, and your queue should hold that contact rather than reassign it to another worker.
The audit log earns its keep on the day something goes wrong. It should record sends, blocked sends, approval decisions and mailbox setting changes, with enough detail that a reviewer can reconstruct who authorized what.
Choosing Your Sending Setup Before Go-Live
Two setups are on the table, and the choice is about identity as much as tooling.
Running your own sending stack means a provider account (Amazon SES, Resend, Postmark or similar), your own SPF, DKIM and DMARC records on each sending domain, and your own queue, webhook handlers and suppression list, all built as described above. It's a known quantity and it's a lot of surface area to own.

The other path gives each agent a mailbox of its own. Molted Email is a mailbox for AI agents the way Gmail serves people: not an API wrapper, not a layer on top of Resend, Postmark or SES, and no separate email provider account to open. Sending runs through POST /v1/agent/request-send (or POST /v1/send/request) with policy enforcement, rate limiting and delivery tracking built in, and agents connect through the REST API, the molted CLI or the MCP server.
If that second path fits, the first actions are concrete:
- Create the account and set up one mailbox for one agent, left on autonomous sending.
- Send one test to an address you own and confirm the status reaches delivered in your event log.
- Add approvals to that mailbox only if the agent's first-contact behavior needs review.
Provider failover is a separate design question with its own trade-offs, covered in multi-provider failover and why you never want to depend on one ESP.
Molted Email gives each agent its own address and inbox, with sending, receiving and delivery included so there's no separate mail provider account to open. Every send is checked against mailbox policy first: duplicates, cooldowns, suppressions and sending limits, with deliveries and bounces arriving as events or signed webhooks. Start free at Molted Email; it's free for people, and paid plans start at $19 a month.