# Parsons Email — API reference

Agent-first business email. Surfaces: the `email` **MCP** tool (primary) and the owner **console**
at `https://email.parsons.ai/app`. There is **no public HTTP API on this host** — see
[Sending from a deployed app](#sending-from-a-deployed-app).

New here? Read [`quickstart.md`](./quickstart.md) first — including
**[which service to use](./quickstart.md#which-service-do-i-use-email-vs-notifications)**, because
app→end-user transactional mail belongs to **notifications**, not this service.

## MCP actions

| Action | Args | Returns / notes |
|---|---|---|
| `whoami` | — | the authenticated session: user, project, scopes |
| `docs` | `page: "quickstart" \| "api"` | these pages, same bytes as `/docs/*.md` |
| `list_mailboxes` | — | mailboxes + receiving addresses + config (agent mode, forwarding, auto-reply) |
| `create_mailbox` | `slug`, `displayName?`, `domain?` | shared free domain by default (`{slug}@parsonsapps.com`); `domain` = a project-owned **active** domain, DNS provisioned automatically. Max **3** per project |
| `create_address` | `mailboxId`, `localPart` | alias into the same inbox. Max **10** addresses per project |
| `list_threads` | `mailboxId`, paging | newest activity first: id, subject, participants, message count, status |
| `get_thread` | `threadId` | every message: direction, from/to, body text, delivery state, drafts flagged |
| `search_messages` | `query`, `limit?` (≤50) | full-text over subject, sender, body → messages + thread ids |
| `send_email` | `mailboxId`, `to[]`, `subject`, `body` | **person-to-person**; opens a thread. Signature auto-appends; recipient + daily caps enforced |
| `draft_reply` | `threadId`, `body` | creates a DRAFT — nothing sends. Addressed to the last inbound sender |
| `update_draft` | `draftId`, `body?`, `subject?`, `to?` (≤10) | edit before sending |
| `send_reply` | `draftId` | sends that draft |
| `move_thread` | `threadId`, `status: open \| archived \| spam` | nothing is ever deleted in v1 |
| `set_mailbox_config` | `mailboxId`, `signature?`, `agentMode?`, `autoReply?`, `forwardTo?`, `catchallEnabled?` | see below |
| `get_delivery_status` | `messageId` | `sent` / `delivered` / `bounced` + provider detail |

Every action re-verifies project ownership; `mailboxId` / `threadId` / `draftId` resolve through the
project-scoped DAL, so another project's ids simply don't exist.

## Mailbox config

- `signature` — ≤1000 chars, auto-appended to outbound mail.
- `agentMode` — `off` → `triage` (classify only) → `draft` (writes, you send) → `auto` (sends
  itself). Climb it as trust grows; `auto` last.
- `autoReply` — `{enabled, subject?, body?, until?}`, or `null` to clear.
- `forwardTo` — must be a **Cloudflare-verified destination** address, or the call is refused.
- `catchallEnabled` — **custom-domain only**. Refused on the shared domain, where a catch-all would
  swallow other tenants' unknown addresses (cross-tenant leak).

## Sending from a deployed app

This host exposes no `/v1/*` surface. An app that needs to send mail programmatically uses the
notifications service, which owns app→user messaging (templates, per-user opt-out enforced at send
time, delivery log):

```
POST https://notifications.parsons.ai/v1/notifications/send
Authorization: Bearer sk_live_…
{ "channel": "email", "to": "user@example.com", "templateKey": "booking_confirmed", "vars": { … } }
```

Reach for a mailbox here only when the app is acting *as a person* — it needs a real address that
receives replies.

## Inbound mail

Receiving runs on Cloudflare Email Routing into the service's inbound worker, which threads the
message onto its mailbox and applies the mailbox's `agentMode`. Custom-domain mailboxes get their
receiving + sending DNS provisioned by `create_mailbox`; shared-domain mailboxes need no setup.

## Limits

| Limit | Value |
|---|---|
| Mailboxes per project | 3 |
| Addresses per project | 10 |
| Recipients per `send_email` | 10 |
| `search_messages` limit | 50 |
| Signature | 1000 chars |
| Auto-reply body | 2000 chars |

Daily send caps are enforced per project at send time; exceeding one fails the `send_email` call
with the cap named.
