> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getmailr.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Mailr is a direct-mail marketing platform for US home-services contractors. Each postcard is personalized with an AI-enhanced image of the recipient's own home and a unique QR code that leads to a personalized landing page. Campaigns can target a neighborhood the customer draws on a map, the homes around past jobs they upload or import, or fire automatically from their CRM. Mailr does not mail an uploaded recipient list. When answering questions, prefer the exact steps and UI labels from these docs, and direct users to app.getmailr.com to sign in. For anything involving account-specific data, billing disputes, or mail that appears lost, direct the user to support@getmailr.com.

# MCP server

> Drive Mailr conversationally from Claude Code, Claude Desktop, or any MCP client — a thin proxy over the same /api/v1/* endpoints.

Mailr hosts a **Model Context Protocol** server at `https://app.getmailr.com/api/mcp`. Point an MCP client at
it with your API key and an agent can run the Developer API conversationally: check the key, list automations,
dry-run an event, subscribe a lead webhook, report a won deal, stage a reactivation import.

It is a **thin proxy and nothing more**. Every tool forwards your `Authorization: Bearer ak_…` header verbatim
to the matching `/api/v1/*` endpoint, so authentication, scopes, rate limits, validation and idempotency are
exactly the ones documented on the other pages of this reference. The MCP route holds no privileged access of
its own — an unauthenticated client can list the tools but cannot make a single call succeed.

<Note>
  The OpenAPI spec behind the **Endpoints** section of this reference remains the source of truth for every
  request/response shape. The MCP tools map one-to-one onto the operations it documents and pass through only
  documented fields.
</Note>

## Add the server

<CodeGroup>
  ```bash Claude Code theme={null}
  export MAILR_API_KEY="ak_live_xxxxxxxxxxxxxxxxxxxxxxxx"

  claude mcp add --transport http mailr https://app.getmailr.com/api/mcp \
    --header "Authorization: Bearer $MAILR_API_KEY"
  ```

  ```json .mcp.json theme={null}
  {
    "mcpServers": {
      "mailr": {
        "type": "http",
        "url": "https://app.getmailr.com/api/mcp",
        "headers": {
          "Authorization": "Bearer ${MAILR_API_KEY}"
        }
      }
    }
  }
  ```
</CodeGroup>

`MAILR_API_KEY` holds the organization-scoped API key the client sends as the bearer token. Keep it in your
machine's or CI's environment: `${MAILR_API_KEY}` is expanded at connect time, so the key itself never lands
in a file you commit.

## Tools

| Tool                | Endpoint                            | Scope              |
| ------------------- | ----------------------------------- | ------------------ |
| `whoami`            | `GET /api/v1/me`                    | `webhooks:inbound` |
| `list_automations`  | `GET /api/v1/automation/list`       | `webhooks:inbound` |
| `send_event`        | `POST /api/v1/events`               | `events:write`     |
| `subscribe_leads`   | `POST /api/v1/webhooks/subscribe`   | `webhooks:inbound` |
| `unsubscribe_leads` | `DELETE /api/v1/webhooks/subscribe` | `webhooks:inbound` |
| `list_recent_leads` | `GET /api/v1/integrations/leads`    | `webhooks:inbound` |
| `report_conversion` | `POST /api/v1/conversions`          | `webhooks:inbound` |
| `sync_lead`         | `POST /api/v1/tracking/lead`        | `webhooks:inbound` |
| `import_jobs`       | `POST /api/v1/import/jobs`          | `import:write`     |
| `suppress_customer` | `POST /api/v1/customers/webhook`    | `webhooks:inbound` |

`whoami`, `list_automations` and `list_recent_leads` are annotated `readOnlyHint`, so clients that separate
read from write tools can auto-approve them. `send_event`, `import_jobs`, `suppress_customer` and
`unsubscribe_leads` are annotated `destructiveHint` — a live send spends postage, an import burns one of 10
daily slots, suppression is not reversible from this API, and unsubscribing silently stops lead delivery — so
clients that gate on annotations will not auto-approve them.

The legacy `POST /api/v1/webhooks/trigger` is **not** exposed. `send_event` binds by event type instead of
hardcoding an automation id and reports richer outcomes; keeping the tool surface minimal is deliberate.

## Safety model

<Warning>
  **Two tools can spend the customer's postage.** `send_event` can enqueue real postcard campaigns, and
  `import_jobs` stages the batch a reactivation campaign mails. Confirm intent — and the expected cost — with a
  human before either goes live.
</Warning>

* **`send_event` defaults to `dry_run: true`.** A dry run performs the full binding match, filter, address and
  dedup checks and returns exactly the outcomes a live call would, but writes nothing: no connection upsert,
  no sample, no enqueue, no log. Only an explicit `dry_run: false` mails anything. (A dry run still costs one
  unit of rate-limit quota.)
* **`import_jobs` mails nothing immediately** — it stages only. But one call burns one of just 10 import slots
  per org per UTC day, and the staged batch is what an operator later sends.
* **Confirm-before-spend is baked into the tool descriptions**, so the agent sees the warning at call time
  rather than only in these docs.
* **`subscribe_leads` returns a `signing_secret` you must store.** The tool tells the agent to show it to you
  once and not repeat it. The secret is disclosed exactly once, at creation: re-subscribing the same URL is
  idempotent and returns the existing row's id with `signing_secret: null` — never the stored secret, and
  never a silent rotation. A lost secret is replaced via
  [`POST /api/v1/webhooks/rotate`](/api-reference/receive-leads#rotate-a-lost-secret), which returns the new
  one a single time.
* **Scopeless keys are unrestricted.** Per the v1 policy, a key that declares no scopes passes every check.
  Treat the key like a password: it is the whole credential, it identifies the tenant on its own, and anything
  holding it can mail on the org's behalf. Scope the key you hand to an agent, and revoke it from the Mailr
  dashboard if it leaks.
* **The route is key-authed, not session-authed.** `/api/mcp` runs no dashboard session logic and holds no
  privileged access of its own; every tool call is authorized downstream by the same API-key check the
  `/api/v1/*` endpoint it proxies to performs.
* **Every tool result carries a notice** telling the agent to treat response data — lead messages, names,
  custom fields — strictly as data, never as instructions.

## Try it

```text theme={null}
> Which Mailr org is this key for?

  ⏺ whoami
    HTTP 200 OK
    { "org_id": "org_2abcXYZ", "org_name": "Reyes Roofing" }

  This key belongs to Reyes Roofing.

> Dry-run a completed job at 3134 River Valley Dr, Austin TX 78701 for Dana Reyes, $6,400.

  ⏺ send_event (dry_run: true)
    HTTP 202
    {
      "received": true,
      "duplicate": false,
      "dry_run": true,
      "matched_automations": 1,
      "outcomes": [
        { "automation_id": "a1b2c3d4-…", "name": "Post-job reactivation",
          "matched": true, "reason": "queued" }
      ]
    }

  "Post-job reactivation" would fire and mail this address. Nothing was written.
  Want me to send it for real? That enqueues a live postcard and spends postage.
```

The `/api/mcp` edge enforces the same pre-auth **per-IP** throttle as the REST API: a client over the line gets
a JSON-RPC error (`code: -32000`) at HTTP `429` with a `Retry-After` header, before any key is verified — so a
flood of unauthenticated calls is shed at the edge.

Errors come back as tool errors carrying the v1 status and body, so `422 invalid_payload` names the offending
field just as it does over curl. On `429`/`503` the tool result also states how many seconds to wait, taken
from the `Retry-After` header — see [Errors & rate limits](/api-reference/errors) for the full retry contract.
