---
title: "How to let an AI agent send email safely"
description: "The concrete controls that make agent-initiated email safe: scoped credentials, a verified-domain gate, idempotency, approval before send, and an audit trail."
url: https://www.unitpost.com/blog/send-email-from-ai-agent
section: Blog
updated: 2026-07-27
---
# How to let an AI agent send email safely

Let an agent send email by giving it a narrowly scoped API key rather than your account, and by putting the same gates in front of it that you'd put in front of any untrusted client: a verified-domain check, per-workspace rate limits, idempotency keys so a retry loop can't send twice, and an approval step for anything one-to-many.

## How to let an AI agent send email safely

> The concrete controls that make agent-initiated email safe: scoped credentials, a verified-domain gate, idempotency, approval before send, and an audit trail.

An agent that can send email is a client that composes its own requests. That is genuinely useful — drafting a follow-up, sending a report, notifying a user — and genuinely risky, because the failure modes differ from a human's. Agents retry, loop, and occasionally misunderstand scope in ways a person wouldn't.

### Give it a scoped key, not your account

The single most important control. Create a dedicated key with only the capability the agent needs, and revoke it independently of everything else. A key that can send email should not also be able to delete contacts or read your logs.

- One key per agent, so revoking one doesn't disrupt the others
- Only the scopes it needs — emails:send for sending, nothing more
- Never a full-access key, and never the key your application uses

### The domain gate does the heavy lifting

An agent can only send from a domain already verified in the workspace that owns its key. It cannot add a domain, cannot verify one, and cannot spoof an address on a domain you don't control — the API returns 403 regardless of how convincingly it asks. This is the control that makes the whole arrangement tractable: the worst case is unwanted mail from your own verified domain, not impersonation of someone else.

### Idempotency, because agents retry

Agent frameworks retry on timeouts and ambiguous errors, and an agent that isn't sure whether a send succeeded will often try again. Send an Idempotency-Key and a retry within 24 hours returns the original result instead of a second email:

**cURL**

```bash
curl https://www.unitpost.com/api/v1/emails \
  -H "Authorization: Bearer pk_live_..." \
  -H "Idempotency-Key: report-run-8f21" \
  -H "Content-Type: application/json" \
  -d '{"from":"Acme <hello@acme.com>","to":"user@example.com","subject":"Your report","html":"<p>Attached.</p>"}'
```

**Node.js**

```ts
await unitpost.emails.send(
  {
    from: "Acme <hello@acme.com>",
    to: "user@example.com",
    subject: "Your report",
    html: "<p>Attached.</p>",
  },
  { idempotencyKey: `report-${runId}` },
);
```

**Python**

```python
unitpost.emails.send(
    {
        "from": "Acme <hello@acme.com>",
        "to": "user@example.com",
        "subject": "Your report",
        "html": "<p>Attached.</p>",
    },
    idempotency_key=f"report-{run_id}",
)
```

> **Derive the key from the task, not the attempt:** Use something stable for the unit of work — a run id, a ticket id — so every retry of the same logical send reuses it. A random key per attempt provides no protection at all.

### Require approval for anything one-to-many

A mistake sent to one person is a mistake. The same mistake sent to a segment is an incident. One-to-many marketing sends are refused on the transactional API entirely — they have to go through campaigns, which has an explicit send step. That structural boundary means an agent with emails:send cannot mail your whole list even if it decides to.

### Keep the audit trail

Every authenticated API request is logged, so 'what did the agent actually send' is answerable after the fact. When you're deciding how much autonomy to grant, being able to review what it did is what makes the decision reversible.

## FAQ

### What stops an agent from emailing the wrong person?

Nothing structural — that's a prompt and application-design problem, and it's the residual risk you're accepting. What the platform does prevent is worse outcomes: sending from a domain you don't own, mailing a whole segment through the transactional API, sending duplicates on retry, and mailing addresses that previously bounced or complained.

### Can I limit an agent to specific recipients?

Not through API scopes, which are capability-based rather than recipient-based. If you need that, keep the recipient list on your side: have the agent call your own endpoint that validates the recipient, and let that endpoint hold the Unitpost key. The agent never sees a credential that can send arbitrarily.

### Should an agent use its own API key or share the app's?

Its own, always. A separate key means you can revoke the agent's access the moment something looks wrong without taking down your application's sending, and the request log tells you unambiguously which sends came from the agent.

## Related

- [What is an email MCP server?](https://www.unitpost.com/blog/email-mcp-server): How Model Context Protocol exposes email sending as agent tools, what a well-designed email MCP surface looks like, and how authentication and scopes work.
- [How to fix an SPF record that isn't found](https://www.unitpost.com/blog/spf-record-not-found): Why an SPF lookup returns nothing even after you've added the record, and how to diagnose it: propagation, wrong host, quoting, and the one-SPF-record rule.
- [Why DKIM verification fails (and how to fix each cause)](https://www.unitpost.com/blog/dkim-verification-failing): The concrete reasons a DKIM check fails after you've published the key: selector mismatch, a truncated public key, DNS flattening, and message modification in transit.
