# fixture.email — docs

Give your agent a real inbox it can send to, receive in, and assert on — wired up in minutes.
Programmable mailboxes on a real domain (SPF/DKIM/DMARC), a JSON send API, signed webhooks,
and a built-in MCP whose agent waits for an email in ONE call instead of polling.
One org-scoped key authenticates both the MCP and direct HTTP.

## Quickstart

1. Mint an org-scoped API key in the keys portal: https://preview.fixture.email/portal
2. Connect the MCP (Claude Code):

       claude mcp add --transport http fixture-email https://preview-mcp.fixture.email/mcp --header "X-API-Key: $KEY"

   One URL and one header — there is no session to open and nothing to keep alive.
   Then: create_mailbox -> send_email -> wait_for_message (one call, no polling loop) -> get_message.

3. Or call the HTTP API directly with the same key:

       # mint a mailbox your agent can send to and assert on
       curl -sS -X POST https://preview-send.fixture.email/api/mailboxes -H "X-API-Key: $KEY"
       # send a test email to it (internal route — off-quota, no app wiring)
       curl -sS -X POST https://preview-send.fixture.email/api/send -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
         -d '{"to":"inbox+<id>@fixture.email","subject":"hello","text":"hi"}'
       # then poll for it to arrive (HTTP only — over the MCP this is one wait_for_message call)
       curl -sS "https://preview-send.fixture.email/api/messages?mailbox=<id>" -H "X-API-Key: $KEY"

## Authentication

Every /api/* call (and the MCP) authenticates with an org-scoped X-API-Key header
(or Authorization: Bearer <key>). The key resolves to your organization — the only
authorization boundary. Mint, list, and revoke keys in the portal or via /api/keys;
an agent can author a scoped key with provision_keys, but only ever gets back a claim
link — a secret is revealed once, in the browser, and never by a tool.
A key whose org has no live trial/subscription is rejected with 402; a suspended org gets 403.

## Core concepts

- Mailboxes & sub-addressing: a mailbox is a 16-char id. Any address of the form
  <anything>+<mailbox>@<domain> routes to it, so one mailbox catches many labelled addresses.
- The loop: create_mailbox -> send (or point your app at the address) -> wait for the message ->
  read its content to assert subject, body, sanitized HTML, links, headers, and SPF/DKIM/DMARC.
- Waiting: over the MCP it is ONE call — wait_for_message returns the mail as soon as it lands
  (immediately if it already has), and wait_for_send_outcome resolves a send to sent/failed/received.
  Never write a retry loop around list_messages.
- Over plain HTTP, reads are polling: poll GET /api/status/{id} or GET /api/messages every 2-3s.
  There are no read webhooks — but you can register signed lifecycle callbacks (below).
- Quota: only EXTERNAL sends count; internal test-domain routing is free. Over the cap -> 429.
- Simulated outcomes: send to delivered@ / bounced@ / complained@ / suppressed@<domain> to
  synthesize a terminal status + callback with no real delivery (no reputation impact).

## HTTP API

Base URL: https://preview-send.fixture.email  ·  Auth: X-API-Key header on every /api/* route.

  POST   /api/send                         build an .eml from JSON and queue it
  POST   /api/send/upload                  send a raw .eml (multipart) + From/To overrides
  POST   /api/attachments/upload           stage an attachment >= 1MB (25MB cap)
  POST   /api/mailboxes                    mint a mailbox id
  GET    /api/mailboxes/{mailbox}          mailbox state
  DELETE /api/mailboxes/{mailbox}          disable (does not schedule cleanup)
  POST   /api/mailboxes/{mailbox}/enable   re-enable
  POST   /api/mailboxes/{mailbox}/disable  disable
  POST   /api/mailboxes/{mailbox}/safe-cleanup    schedule grace-period delete
  POST   /api/mailboxes/{mailbox}/cancel-cleanup  cancel it
  GET    /api/status/{id}                  one email status + metadata
  GET    /api/messages?mailbox=<id>        list/filter messages (polling)
  GET    /api/messages/{id}/content        parsed html/text/links/headers + auth results
  GET    /api/messages/{id}/raw            presigned URL to the raw .eml (15 min)
  GET    /api/usage                        outbound quota + reputation snapshot
  POST   /api/keys · GET /api/keys · DELETE /api/keys/{id}    manage API keys
  POST   /api/callbacks · GET · DELETE /api/callbacks/{id}    manage webhooks

### Send

  POST /api/send  { from?, to, subject, text?, html?, cc?, bcc?, attachments?, headers? }
  - from is optional: omit it to auto-create a mailbox; a full +<mailbox> address is used as-is.
  - to is required. cc/bcc accept a string or an array.
  - attachments < 1MB inline as base64 `content`; >= 1MB pre-upload and pass `attachmentId`.
  - headers carries X-* custom MIME headers only; they round-trip to /content.headers.
  Returns { emailId, mailbox, from }. Poll status/messages with emailId.

### Quota (read GET /api/usage for your exact, scaled caps)

  - Trial: 50 external sends/day, 250 total across the trial. A trial 429 carries a checkoutUrl.
  - Paid: 500/day and 15,000/month per purchased unit (buy N units to scale).
  - Bounces/complaints add the recipient to your suppression list (later sends -> 403);
    sustained abuse auto-suspends the org (all /api/* -> 403 until support restores it).

## MCP

Connect: https://preview-mcp.fixture.email/mcp  (header X-API-Key: <key>, same org-scoped key as the HTTP API).
One URL, one header: no session, no handshake, nothing to keep open. Every request is
authenticated and entitlement-checked on its own.

15 tools: whoami, create_mailbox, list_messages, wait_for_message, get_message,
render_message_view, send_email, wait_for_send_outcome, list_api_keys, revoke_api_key,
provision_keys, render_grant_builder, register_callback, list_callbacks, delete_callback.

- wait_for_message replaces the polling loop: one call, returns the message as soon as it
  arrives (and immediately if it already has); expiry hands back a cursor, not an error.
- wait_for_send_outcome does the same for a send, which returns as soon as it is QUEUED.
- get_message also RENDERS the email in clients that support MCP Apps — body, links, headers
  and auth verdicts, with remote content blocked and every blocked URL reported. Other clients
  get the same JSON. render_message_view is that viewer’s own tool, not one to call yourself.
- provision_keys authors scoped keys as inert claim links; a human reveals each secret once in
  the portal. Key secrets are never returned by a tool. In an MCP Apps client it also opens a
  grant BUILDER — tier, mailbox set, message classes, actions and send flags, with the resulting
  role shown before anything is authored. render_grant_builder is that builder’s own tool.

## Webhooks

Register an HTTPS endpoint (POST /api/callbacks or register_callback) for signed, at-least-once
events: email.sent, email.failed, email.received, email.bounced, email.complained, email.suppressed,
plus org.warning / org.suspended. The signing secret is shown once. Verify each delivery:

  X-Webhook-Signature = "sha256=" + hex(HMAC_SHA256(secret, timestamp + "." + rawBody))

Recompute over the X-Webhook-Timestamp header + raw body and constant-time-compare. Dedupe on X-Webhook-Id.

## Errors

  { "error": "..." }   400 bad payload · 401 missing/invalid key · 402 not entitled ·
  403 suppressed/suspended · 404 not found · 413 too large · 429 quota/rate-limit · 500 server error

## Get these docs into your agent

- Machine-readable crib (point your agent here): https://preview.fixture.email/llms.txt
- MCP endpoint: https://preview-mcp.fixture.email/mcp
- OpenAPI spec (key-gated, field-level source of truth): https://preview-send.fixture.email/api/openapi.json — Swagger UI: https://preview-send.fixture.email/api/docs
- API catalog: /.well-known/api-catalog

Tip: paste this whole page into your agent with "Copy as Markdown".