API Docs

Transactional email

Queue application-generated email through a native HTTP API, Mailgun-compatible clients, MCP tools, or an A2A agent.

Examples use API_BASE_URL or clearly marked example.com placeholders instead of a deployment URL. The Console shows the correct URLs for your deployment under Settings.

Choose an integration

  • Native JSON API

    Recommended for new server-side integrations and full To, Cc, Bcc, and Reply-To control.

  • Mailgun-compatible API

    Use when migrating a supported Mailgun sending integration or using mailgun-go.

  • MCP tools

    Give an MCP-capable agent structured tools to discover sender domains, send email, and inspect delivery state.

  • A2A agent

    Use the A2A JSON-RPC contract when one agent delegates a transactional send to another agent.

Before you send

  1. Create an organization read_write API token in Console Settings. Token secrets are displayed only once and must stay server-side.
  2. Accept the currently effective Terms and Privacy version for the organization.
  3. Use an enabled sender domain owned and currently authorized for the organization. The visible From address must use that domain.

A read token may inspect outbound messages, but it cannot send. Browser sessions cannot call the sending interfaces.

Native JSON API

POST /api/v1/outbound-messages

Authenticate with Authorization: Bearer <token> and send Content-Type: application/json. JSON is strict: unknown fields are rejected.

curl --fail-with-body "$API_BASE_URL/api/v1/outbound-messages" \
  --request POST \
  --header "Authorization: Bearer $EMAIA_API_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: order-123-confirmation" \
  --data '{
    "from": "Store <receipts@sender.example>",
    "to": ["Customer <customer@example.net>"],
    "replyTo": "Support <support@sender.example>",
    "subject": "Your receipt",
    "text": "Thank you for your order."
  }'

from and subject are required. Supply at least one recipient across to, cc, and bcc, and at least one of text or html. Mailboxes may be RFC mailbox strings such as Store <receipts@sender.example> or objects with name and email fields.

HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "id": "outbound-message-id",
  "messageId": "<generated-message-id@sender.example>",
  "status": "queued"
}

A 202 response means the message was accepted and queued. It does not mean every recipient has received it.

Mailgun-compatible API

POST /api/compat/mailgun/v3/{domain}/messages

Use HTTP Basic authentication with username api and the organization read_write token as the password. The domain in the URL must exactly match the normalized domain of the from mailbox.

Requests may use multipart/form-data orapplication/x-www-form-urlencoded. Repeatto, cc, or bcc fields for multiple recipients. h:Reply-To is the only supported custom header field.

curl --fail-with-body \
  --user "api:$EMAIA_API_TOKEN" \
  --header "Idempotency-Key: order-123-confirmation" \
  --form "from=Store <receipts@sender.example>" \
  --form "to=Customer <customer@example.net>" \
  --form "h:Reply-To=Support <support@sender.example>" \
  --form "subject=Your receipt" \
  --form "text=Thank you for your order." \
  "$API_BASE_URL/api/compat/mailgun/v3/sender.example/messages"

mailgun-go

mg := mailgun.NewMailgun("sender.example", os.Getenv("EMAIA_API_TOKEN"))
mg.SetAPIBase(os.Getenv("API_BASE_URL") + "/api/compat/mailgun")

message := mailgun.NewMessage(
    "Store <receipts@sender.example>",
    "Your receipt",
    "Thank you for your order.",
    "Customer <customer@example.net>",
)
message.SetReplyTo("Support <support@sender.example>")

id, response, err := mg.Send(ctx, message)

The SDK base is $API_BASE_URL/api/compat/mailgun. The SDK appends /v3/{domain}/messages; thatv3 is part of Mailgun compatibility, not the native API version.

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "<generated-message-id@sender.example>",
  "message": "Queued. Thank you."
}

Errors use a Mailgun-shaped JSON object with a singlemessage field. Bearer authentication is intentionally rejected on this route family.

Shared sending contract

  • At most 100 recipients remain after deduplication.
  • Recipients are deduplicated case-insensitively across To, Cc, and Bcc. The first occurrence keeps its kind and order.
  • Bcc recipients receive the message but are omitted from visible message headers.
  • The request limit is 1 MiB; subject is limited to 500 Unicode characters, plain text to 256 KiB, and HTML to 512 KiB.
  • Version 1 does not support attachments, inline files, templates, raw MIME, scheduled delivery, test mode, tags, metadata, custom variables, recipient variables, AMP HTML, or arbitrary headers.
  • Tracking options are unsupported and the service does not inject open-tracking pixels. Customer-provided HTML is not rewritten to remove external tracking.

You remain responsible for lawful message content, recipient selection, notices, consent or another legal basis, and any external tracking, including GDPR and ePrivacy obligations.

Idempotency and retries

Send an optional Idempotency-Key header with native or Mailgun-compatible HTTP requests. MCP usesidempotencyKey; A2A uses the A2A message ID. Keys are case-sensitive, opaque, and limited to 255 bytes.

Reuse a key only when retrying the same logical send. The same key and normalized payload replay the original accepted result without consuming accepted-send or recipient quota. Reusing a key with a different payload is a conflict. Keys are isolated between the native, Mailgun-compatible, MCP, and A2A interfaces.

Without an idempotency key, retrying after an uncertain network result may queue a duplicate message.

Rate limits

  • 100 accepted send requests per minute per API token.
  • 1,000 accepted, deduplicated recipients per hour per organization.
  • Rejected requests do not consume accepted-send or recipient quota.

HTTP integrations receive 429 Too Many Requests with a Retry-After header in seconds. MCP tool errors and A2A error details expose an equivalent retry delay.

HTTP errors

  • 400: malformed or invalid fields.
  • 401: missing, invalid, expired, or revoked credentials.
  • 403: insufficient token scope, required legal acceptance, or unauthorized sender domain.
  • 409: an idempotency key was reused with a different payload.
  • 413: the request exceeds 1 MiB.
  • 415: unsupported request content type.
  • 429: an accepted-send or recipient limit was reached; follow Retry-After.
  • 500: an unexpected server error; retry cautiously with the same idempotency key.

Native API errors use an error object containingcode and message. Mailgun-compatible errors use its compatibility shape with a top-levelmessage.

Delivery status

GET /api/v1/outbound-messages

GET /api/v1/outbound-messages/{id}

Use Bearer authentication with an organization reador read_write token. The list returns recent messages, newest first. The detail response includes the message, individual recipient states, and state-change events.

Treat queued as acceptance for asynchronous delivery, not final delivery. Follow recipient states such as smtp_accepted, delivered,deferred, bounced, expired, and failed.

MCP tools

POST /mcp

Connect with MCP Streamable HTTP and send an organization read_write token as a Bearer credential. The endpoint is stateless and accepts request bodies up to 1 MiB.

The examples below use https://example.com/mcp as a placeholder. The real MCP endpoint for your deployment is available in Console Settings under Integration endpoints.

Codex

Add the server to your Codex configuration and replace the token placeholder with a read_write API token.

[mcp_servers.transactional_email]
url = "https://example.com/mcp"
http_headers = { Authorization = "Bearer YOUR_READ_WRITE_TOKEN" }
enabled = true
default_tools_approval_mode = "writes"

Claude Code

Register the HTTP server with the Claude Code CLI and replace the token placeholder with a read_write API token.

claude mcp add --transport http --scope user transactional-email https://example.com/mcp \
  --header "Authorization: Bearer YOUR_READ_WRITE_TOKEN"
  • list_sender_domains lists active sender domains and whether each can send.
  • send_email queues one message. It accepts from, one to 100 to mailboxes,subject, optional text and html, and a required idempotencyKey.
  • get_email_delivery_status accepts the returned outboundMessageId and returns message and recipient states without message bodies or provider diagnostics.
{
  "from": "Store <receipts@sender.example>",
  "to": ["Customer <customer@example.net>"],
  "subject": "Your receipt",
  "text": "Thank you for your order.",
  "idempotencyKey": "order-123-confirmation"
}

A successful send_email result contains outboundMessageId, messageId, and status queued. Tool errors return structured codes such as invalid_request, legal_acceptance_required, sender_not_authorized, idempotency_conflict, and rate_limited.

A2A agent

GET /.well-known/agent-card.json

POST /a2a

Discover the public agent card, then call the advertised A2A JSON-RPC interface with an organization read_writeBearer token. The agent exposes the send-email skill with JSON input and output.

Send one A2A user message containing exactly oneapplication/json data part with this shape:

{
  "from": "Store <receipts@sender.example>",
  "to": ["Customer <customer@example.net>"],
  "subject": "Your receipt",
  "text": "Thank you for your order."
}

The A2A message ID is the idempotency key. A successful send returns a completed A2A task whose artifact containsoutboundMessageId, messageId, andqueued. “Completed” means the delegation and queueing operation completed; it does not mean final email delivery.

Task lookup and paginated task listing are supported. Cancellation, streaming, subscriptions, push notifications, tenant overrides, and extended agent cards are not supported.