# Email API for AI agents

An autonomous agent needs seven things from an email API: errors that carry a stable code and a corrective action, idempotent requests, a dry run that sends nothing, keys bounded by scope and by volume, a delivery status readable per message, an audit log of every call, and documentation a machine can parse. VaEmail implements all seven, and this page names the endpoint or the MCP tool behind each one. The REST API is at `https://app.vaemail.fr/api/v1`, servers are in Germany, sending is done in Europe.

Facts checked against the VaEmail documentation on 6 September 2026.

## Why a program needs different guarantees than a person

A person who makes a mistake stops. A program that makes a mistake starts over. It retries after a timeout, it never opens a dashboard, and it reports success because it received a 2xx. Every guarantee below exists to make those three behaviours harmless.

## Structured errors: a stable code and a corrective action

Every VaEmail error body carries an `error` block alongside the historical `success` and `message` fields:

```json
{
  "success": false,
  "message": "Le domaine expéditeur n'est pas dans ceux autorisés pour cette clé.",
  "error": {
    "code": "SENDER_DOMAIN_NOT_ALLOWED",
    "message": "Le domaine expéditeur n'est pas dans ceux autorisés pour cette clé.",
    "retryable": false,
    "resolution": { "action": "use_allowed_sender_domain" },
    "documentation": "https://vaemail.fr/docs/errors#sender_domain_not_allowed"
  }
}
```

`code` is stable: codes get added, never renamed, so a program can branch on it instead of on message text. `retryable` says whether the identical call can succeed later; when it is `false`, retrying unchanged is wasted work. `resolution` names the action, and sometimes the endpoint that performs it. Fourteen codes are documented, each with what to do about it, at https://vaemail.fr/docs/errors.md

Through MCP, an API error does not surface as a transport failure. It comes back as a tool result marked in error, carrying the code, the corrective action and whether a retry is worth anything:

```
RECIPIENT_SUPPRESSED: Destinataire dans la liste de suppression.
Action corrective : remove_from_suppression_list (DELETE /api/v1/suppressions/{email})
Réessayer à l'identique ne changera rien.
```

## Idempotency: a retry does not send twice

`POST /api/v1/transactional/send` accepts an `Idempotency-Key` header. The response is memorised for 24 hours and replayed identically, with `Idempotent-Replay: true`. Derive the key from the business action, not from a random value, so a retry resolves to the same key.

Reusing the same key with a different body returns `409 IDEMPOTENCY_KEY_REUSED`. That is a caller bug, not a replay: replaying the first response would answer a request nobody made. Server errors (5xx) are deliberately not memorised, so a retry with the same key stays safe. `POST /api/v1/campaigns/send` and `POST /api/v1/domains` accept the header too.

## Dry run: POST /api/v1/messages/validate

Before the first real send, and after any configuration change, `POST /api/v1/messages/validate` answers whether the send would go out. Nothing leaves, and nothing is consumed. The response returns `sendable`, a `blocking` list using the same codes as real errors, plus `quota_remaining` and `daily_key_remaining`. Suppressed recipients are caught here rather than on a live send.

## Scoped and capped keys

Eight scopes exist: `email.send`, `email.read_status`, `domain.read`, `domain.write`, `contacts.read`, `contacts.write`, `analytics.read`, `billing.read`. A key that declares none has all of them. A call outside its scope returns `403 INSUFFICIENT_SCOPE` naming both the missing scope and the ones the key holds, so an agent can say precisely what it lacks. The reference states the scope each endpoint requires, and OpenAPI carries it as `x-vaemail-scope`.

Three caps sit on the key itself. `max_emails_per_day` returns `429 DAILY_LIMIT_REACHED`, flagged `retryable: true` since the cap resets. `max_recipients_per_email` returns `TOO_MANY_RECIPIENTS`. `allowed_domains` returns `403 SENDER_DOMAIN_NOT_ALLOWED` for any other sender domain. `GET /api/v1/usage` reports the monthly quota, today's sends, and for the current key its scopes, its daily cap and what remains: an agent that does not know what is left cannot decide to stop.

The documentation recommends a dedicated key per agent, named after it, with `email.send` and `email.read_status`, `max_recipients_per_email` at 1 for transactional mail, and the single planned sender domain. Creating it is a human step: there is no signup endpoint.

## Delivery status, message by message

A send returns `202` with an id. That means accepted and queued, not delivered.

```json
{ "success": true, "data": { "id": 2841, "status": "queued" } }
```

`GET /api/v1/messages/{id}` is the only call that authorises saying an email arrived. `status` is one of `queued`, `sent`, `delivered`, `failed`, `suppressed`, `held`, `cancelled`. On failure, `error` carries the reason returned by the transport and `events` the sequence of what happened, including opens, clicks, bounces and complaints. The MCP tool descriptions repeat this, so an agent does not announce a delivery it cannot know about.

When mail lands badly across a domain, `GET /api/v1/deliverability/diagnose` returns the live SPF, DKIM and DMARC state read from public DNS, the reputation issues ranked by severity, recommended actions with the endpoint behind each, and delivery rates per recipient domain. `GET /api/v1/suppressions` lists the addresses excluded from sending and why. More in [AI agent email deliverability](https://vaemail.fr/ai-agent-email-deliverability).

## Audit log: autonomous, still accountable

`GET /api/v1/audit-logs` returns one line per call: key, agent, operation, parameters, result, emails engaged. Filters are `operation`, `since`, `limit` and `cursor`. Message bodies, attachments and merge variables never appear: enough to understand the action, not enough to read the mail. It lets an agent report what it did instead of reconstructing it from memory.

## Documentation a machine can read

Four machine surfaces, all public:

- `https://vaemail.fr/llms.txt` and `https://vaemail.fr/llms-full.txt`, the whole documentation in one file.
- `https://app.vaemail.fr/openapi.json`, OpenAPI 3.1, generated from the routes actually served, covering 63 endpoints. It carries `x-vaemail-scope` per operation and marks irreversible calls `x-vaemail-destructive: true`, `POST /v3/emailCampaigns/{id}/sendNow` among them.
- `https://app.vaemail.fr/api/v1/capabilities`, which needs no key at all. The call that decides whether VaEmail covers a need before an account exists.
- The MCP server, 18 tools over stdio, zero dependencies, Node 18 and nothing else. Read-only tools carry the `readOnlyHint` annotation. Registry entry: `io.github.vaemail/vaemail`. Details in [MCP email server](https://vaemail.fr/mcp-email-server).

An agent skill at https://vaemail.fr/skill states the rules to follow before the first call (`npx skills add vaemail/skills`). Runnable examples for curl, Node, Python, Claude Code, Codex, the OpenAI Agents SDK, LangChain, CrewAI and n8n live at https://github.com/vaemail/examples

For the Claude Code setup, see [Email for Claude Code](https://vaemail.fr/email-for-claude-code).

## The whole flow, three calls

```bash
# 1. Dry run. Nothing is sent, nothing is consumed.
curl -X POST https://app.vaemail.fr/api/v1/messages/validate \
  -H "api-key: $VAEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"client@exemple.fr","html":"<p>Bonjour</p>"}'

# 2. Send. 202 means accepted, not delivered.
curl -X POST https://app.vaemail.fr/api/v1/transactional/send \
  -H "api-key: $VAEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: facture-2412" \
  -d '{"to":"client@exemple.fr","subject":"Votre facture","html":"<p>Bonjour</p>"}'

# 3. Delivery. Only "delivered" lets you say it arrived.
curl https://app.vaemail.fr/api/v1/messages/2841 \
  -H "api-key: $VAEMAIL_API_KEY"
```

Installing the MCP server instead of writing HTTP calls:

```bash
claude mcp add vaemail --env VAEMAIL_API_KEY=swm_your_key -- npx -y vaemail mcp
npx vaemail init
```

## Copy this to your agent

```
Integrate VaEmail into this project as the outbound email provider.
Official documentation for agents: https://vaemail.fr/llms.txt
OpenAPI specification: https://app.vaemail.fr/openapi.json
What the service supports, without a key: https://app.vaemail.fr/api/v1/capabilities

Rules:
- The API key is in the VAEMAIL_API_KEY environment variable. Do not ask for it,
  never write it into a versioned file.
- Before any real send, call POST /api/v1/messages/validate and stop if
  "sendable" is false.
- Set an Idempotency-Key header on every send, derived from the business action.
- A 202 means accepted, not delivered: read GET /api/v1/messages/{id} before
  announcing a delivery.
- Publishing DNS records, creating an account and paying are human steps: name
  them, never declare them done.
```

## How this compares

On the guardrails above, VaEmail is the only one of the five providers compared on 6 September 2026 that combines scopes, a daily cap, a recipient cap and a sender-domain allow-list on the same key, with a dry run that consumes no quota and an audit log by API. Resend documents `Idempotency-Key` for 24 hours but no per-key caps and no free dry run. Brevo uses an `idempotencyKey` JSON field valid 30 minutes, its sandbox validates format only, and its audit log is Enterprise. Postmark documents no idempotency header at all. AgentMail has fine-grained scopes and the same 24 hour idempotency, and it creates inboxes on demand, which VaEmail does not. Every cell, with the official URL behind it, is at [vaemail.fr/compare/index.md](https://vaemail.fr/compare/index.md).

## Where VaEmail is behind

- **No inbox created on the fly.** An agent cannot obtain an address to receive a verification code or a human reply. Inbound exists only for prospecting sequences, through `GET /v3/outreach/inbox`, attached to a declared IMAP mailbox.
- **A Python SDK only** (`pip install vaemail`). Brevo clients also work against `/v3` unchanged, which is the migration path.
- **Local MCP only**, over stdio. A separate remote connector at `mcp.vaemail.fr`, with OAuth, exposes 16 tools.
- **Six webhook events** (sent, delivered, bounce, complaint, opened, clicked) against fifteen to nineteen elsewhere, with a documented signature (https://vaemail.fr/docs/webhooks.md).
- **Documentation partly in French**, including the error messages returned by the API.
- **A young product, run by an agency**, not a funded company. Prices are public on https://vaemail.fr/tarifs/.

Human steps to plan for: creating the account and the key, publishing DNS records at the registrar, paying or raising a quota, sending a campaign to a whole list. A DNS record flagged `publishable: false` still holds a value that depends on the sending server; publishing it as-is would break the domain's authentication silently. How to run under those constraints: [Autonomous agent email](https://vaemail.fr/autonomous-agent-email).

## Links

- Agent page: https://vaemail.fr/agents
- Connecting an agent: https://vaemail.fr/docs/agents.md
- Quickstart: https://vaemail.fr/docs/quickstart.md
- API reference: https://vaemail.fr/docs/api.md
- Error codes: https://vaemail.fr/docs/errors.md
- Security, scopes, caps, audit log: https://vaemail.fr/docs/security.md
- MCP server and CLI: https://vaemail.fr/docs/mcp.md
- Domain authentication: https://vaemail.fr/docs/domains.md
- Deliverability: https://vaemail.fr/docs/deliverability.md
- Index for language models: https://vaemail.fr/llms.txt
- Whole documentation in one file: https://vaemail.fr/llms-full.txt
- OpenAPI 3.1: https://app.vaemail.fr/openapi.json
- Capabilities, no key: https://app.vaemail.fr/api/v1/capabilities
- Agent skill: https://vaemail.fr/skill
- Comparison with sources: https://vaemail.fr/compare/index.md
- MCP server source: https://github.com/vaemail/vaemail-mcp
- Examples: https://github.com/vaemail/examples
