# Email for n8n AI agents

VaEmail gives an n8n workflow outbound email through three HTTP Request nodes: a dry run, a send carrying an `Idempotency-Key`, and a read of the delivery status. The runnable reference is `vaemail-send.workflow.json` in the public examples repository. Import it, attach one Header Auth credential, and it validates first and sends only when the API says the message would go out. Before the first run the sending domain must be declared and authenticated: `POST /api/v1/domains`, publish the returned DNS records (a human step), then `POST /api/v1/domains/verify`. Until then the dry run answers `DOMAIN_NOT_VERIFIED`. The sender address is the one configured on the account, which is why the body carries no `from`. A send answers 202, which means accepted, not delivered.

Facts checked against the VaEmail documentation on 6 September 2026.

## What the shipped workflow does

The example is a deterministic workflow, not an agent. Its nodes, in order: a Manual trigger, a Code node named `Message` that emits `order_id`, `to`, `subject` and `html`, shipped with sample values (`order-8812`, `client@example.com`) that must be replaced or wired to the real trigger before the first run, an HTTP Request node `Dry run`, an IF node `Sendable?`, an HTTP Request node `Send`, a `Wait` of 5 seconds, and an HTTP Request node `Delivery status`. The false branch of the IF is empty, so a message the API would refuse is never sent. The reason is in `data.blocking` on the dry run response, with the same codes as real errors: read it in that branch instead of ending silently.

That ordering is the same discipline every VaEmail example follows: dry run, send with an idempotency key, read the status before claiming anything.

## The credential

Create one credential of type Header Auth, named `VaEmail api-key`, with name `api-key` and your key as the value. Select it on all three HTTP Request nodes.

Every route except `GET /api/v1/capabilities` expects the key in that header. `X-Api-Key` is accepted too, for compatibility.

## The three HTTP Request nodes

The values below are shown as they appear in the workflow JSON: the leading `=` marks an n8n expression and is not typed when you fill the field in the interface.

Dry run, `POST https://app.vaemail.fr/api/v1/messages/validate`, JSON body:

```
={{ JSON.stringify({ to: $json.to, subject: $json.subject, html: $json.html }) }}
```

It says whether the send would pass (`data.sendable`), lists what blocks it in `data.blocking` with the same error codes as a real send, and returns `quota_remaining` and `daily_key_remaining`. Nothing is sent and nothing is consumed. The false branch of the IF is empty in the shipped file: wire it to a node that surfaces `data.blocking`, otherwise a refusal leaves no trace. The IF node reads it as:

```
={{ $json.data.sendable }}
```

Send, `POST https://app.vaemail.fr/api/v1/transactional/send`, with one header parameter:

```
Idempotency-Key: ={{ $('Message').item.json.order_id }}-confirmation
```

and the body:

```
={{ JSON.stringify({ to: $('Message').item.json.to, subject: $('Message').item.json.subject, html: $('Message').item.json.html }) }}
```

Delivery status, a GET with the id returned by the send:

```
=https://app.vaemail.fr/api/v1/messages/{{ $('Send').item.json.data.id }}
```

The transactional send endpoint requires the `email.send` scope and is rate limited to 60 requests per minute. Reading a message requires `email.read_status`.

## Idempotency, which matters more in a workflow than in a script

An n8n execution that fails halfway gets retried, by a person clicking or by a retry setting. Without `Idempotency-Key`, a retry sends the email again. With it, VaEmail replays the first response for 24 hours and marks the replay with `Idempotent-Replay: true`. That marker is a response header, so the node needs its option to include response headers and status before the workflow can act on it.

The key must be derived from the business action, which is exactly what the example does with `order_id`. Reusing the same key with a different body is refused with `409 IDEMPOTENCY_KEY_REUSED`: that is a bug in how the key is built, not a replay. Server errors (5xx) are not memorised, so retrying them with the same key is safe.

## Reading delivery, not assuming it

`POST /api/v1/transactional/send` answers 202:

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

Queued is not delivered. `GET /api/v1/messages/{id}` returns a `status` among `queued`, `sent`, `delivered`, `failed`, `suppressed`, `held` and `cancelled`, plus `events` and, on failure, the transport reason in `error`.

Only `delivered` supports telling anyone the message arrived. The example waits 5 seconds before reading, which is usually too early for a final verdict: confirmation depends on the recipient's provider, so loop back through the Wait node until `status` reaches a terminal value: `delivered`, `failed`, `suppressed`, `cancelled` or `held`, as examples/curl/send.sh does.

## The key you hand to the workflow

A workflow that runs unattended should not hold a full-access key. Create a dedicated one and bind it at creation:

- scopes: `email.send` and `email.read_status` are enough for this workflow; add `domain.read` and `domain.write` only if something in n8n configures the sending domain; add `billing.read` and `analytics.read` if the workflow reads its own usage or audit log;
- `max_emails_per_day`: the volume the workflow is supposed to produce;
- `max_recipients_per_email`: 1 for transactional mail;
- `allowed_domains`: the single sender domain planned.

The send body carries no free `from` address: the sender is the one configured on the account (`GET /v3/senders`), and the body accepts `from_name` and `reply_to` only.

A call outside its scopes gets a 403 `INSUFFICIENT_SCOPE` naming the missing scope. Over the daily cap, a 429 `DAILY_LIMIT_REACHED` marked `retryable: true`. Sending from another domain, a 403 `SENDER_DOMAIN_NOT_ALLOWED`. `GET /api/v1/usage` (scope `billing.read`) returns what remains, and `GET /api/v1/audit-logs` (scope `analytics.read`) returns one line per call made by that key. Revoking that key breaks nothing else in the account.

## Using it from an n8n AI Agent node

The shipped example contains no AI Agent node, and the VaEmail documentation does not describe wiring these calls as agent tools inside n8n. What it does document is the shape the calls must keep, whoever makes them: validate, send with a key, read the status.

If an AI Agent node drives the sending in your own workflow, the guardrails move to the API key rather than to the prompt. A model can be told to validate first, and nothing in the API enforces that order. What a scoped key does enforce is the ceiling on a wrong decision: `max_emails_per_day`, `max_recipients_per_email` at 1, and `allowed_domains` limited to the planned sender domain. Give the agent branch its own key, keep `max_recipients_per_email` at 1, and read `GET /api/v1/audit-logs` (scope `analytics.read`, so add it to the agent's key) to see what it actually did.

VaEmail's MCP server is another route to the same steps, eighteen tools covering validate, send, message status, domains, deliverability, usage and the audit log, and a remote connector at `mcp.vaemail.fr` with OAuth has its own list of 16 tools, oriented towards campaigns. Hosted automation platforms should check that the tools they need are on the remote list (see https://vaemail.fr/docs/mcp.md). See [mcp-email-server](https://vaemail.fr/mcp-email-server).

## About the Brevo node

VaEmail exposes a Brevo-compatible API at `https://app.vaemail.fr/v3` with the same `api-key` header, and the comparison page states that the seven Brevo SDKs work on it by changing the base URL ([VaEmail vs Brevo](https://vaemail.fr/compare/vaemail-vs-brevo.md), read 06/09/2026). Whether the n8n Brevo node can be pointed at another base URL is not documented on either side, so treat it as untested and use HTTP Request nodes. `POST /v3/smtp/email` exists and is rate limited to 300 requests per minute, but for new work `POST /api/v1/transactional/send` is the one that returns structured errors.

## Limits worth knowing before you build

No inbox is created on demand, so an n8n agent cannot get an address to receive a verification code or a human reply. Inbound exists only for prospecting sequences attached to a declared IMAP mailbox. A Python SDK exists (`pip install vaemail`). Six webhook events are published, with a documented signature (https://vaemail.fr/docs/webhooks.md): sent, delivered, bounce, complaint, opened, clicked, against fifteen email events at Brevo and nineteen signed events at Resend (https://vaemail.fr/compare/index.md, competitor documentation read 6 September 2026). Part of the documentation is in French. The product is young and run by an agency. Prices are public on https://vaemail.fr/tarifs/.

Servers are in Germany, sending is done in Europe.

## Copy this to your agent

```
Use VaEmail as the outbound email provider for this n8n workflow.

Reference workflow: https://github.com/vaemail/examples (n8n/vaemail-send.workflow.json)
Documentation for agents: https://vaemail.fr/llms.txt
OpenAPI specification: https://app.vaemail.fr/openapi.json

Rules:
- Authentication is one n8n Header Auth credential named VaEmail api-key: header name
  api-key, value the API key, entered in the credential only. Never put the key in a
  node parameter or in the workflow JSON.
- Before any real send, call POST https://app.vaemail.fr/api/v1/messages/validate and
  stop if data.sendable is false.
- Every call to POST https://app.vaemail.fr/api/v1/transactional/send carries an
  Idempotency-Key header derived from the business action, not a random value.
- A 202 means accepted, not delivered. Read GET https://app.vaemail.fr/api/v1/messages/{id}
  before reporting a delivery, and only status "delivered" counts.
- Branch on error.code, never on the message text. The HTTP Request nodes must be set to
  continue on error (error output enabled), otherwise a 4xx aborts the execution and the
  error block is never read. See https://vaemail.fr/docs/errors.md
- Creating the API key, publishing DNS records and sending a campaign to a whole list are
  human steps: name them, never declare them done.
```

## Links

- Reference workflow and every other example: https://github.com/vaemail/examples
- Agent page: https://vaemail.fr/agents
- Quickstart: https://vaemail.fr/docs/quickstart.md
- Domains and DNS: https://vaemail.fr/docs/domains.md
- API reference: https://vaemail.fr/docs/api.md
- Error codes: https://vaemail.fr/docs/errors.md
- Scopes, caps, idempotency and the audit log: https://vaemail.fr/docs/security.md
- Deliverability diagnosis: https://vaemail.fr/docs/deliverability.md
- Full documentation in one file: https://vaemail.fr/llms-full.txt
- OpenAPI 3.1: https://app.vaemail.fr/openapi.json
- Capabilities, no key required: https://app.vaemail.fr/api/v1/capabilities
- Comparison with Resend, Brevo, Postmark and AgentMail: https://vaemail.fr/compare/index.md
- Sibling pages: [email-api-for-ai-agents](https://vaemail.fr/email-api-for-ai-agents), [mcp-email-server](https://vaemail.fr/mcp-email-server), [brevo-alternative-for-ai-agents](https://vaemail.fr/brevo-alternative-for-ai-agents), [ai-agent-email-deliverability](https://vaemail.fr/ai-agent-email-deliverability)
