# Coming from Agent Mail

If you have built on Agent Mail, the core loop is familiar: create an inbox, send and receive email,
work with threads, wait for verification messages, and react to webhooks. Extrovert adds a review step
to outbound mail and uses folders, read state, and full-text search instead of labels.
**Signup availability:** Public self-signup can be paused. Check `GET /v1/signup-status` before offering it. Enrollment keys
  are the dependable way to create an agent. An API key connects an agent that already exists.

## Concept mapping

| Agent Mail | Extrovert | Notes |
|---|---|---|
| Client or API key | Agent API key (`pk_agent_...`) | The key fixes the organization, project, and permission ceiling. |
| Create a client | Redeem an enrollment key | `POST /v1/enroll` creates or reuses an agent by `agent_handle` and returns its API key. |
| Resource namespace | Project | Agents, inboxes, domains, and keys belong to a project. |
| Inbox | Inbox | A full email account for sending and receiving, with optional metadata. |
| Thread | Thread | Addressed by an opaque, stable `thread_id`; clients never key conversations by subject. |
| Message | Message | Source `text` and `html` fields stay faithful to MIME. `extracted_text` and `extracted_html` are derived views. |
| Labels | Folders, read state, and search | A message has one folder and standard read or unread state. |
| Webhooks | Webhooks | Signed deliveries for inbound messages and other configured events. |
| Wait for message | `wait_for_email` | Waits server-side and can return a matching OTP or link. |
| Framework adapters | MCP server or TypeScript SDK | Use hosted MCP from an agent host, or call the API through the SDK. |
| No equivalent | Review Loop | Every agent-plane send creates a review record. The inbox policy decides whether it waits or is released. |

## Endpoint mapping

| Task | REST endpoint | TypeScript SDK | MCP tool |
|---|---|---|---|
| Create or reuse an agent | `POST /v1/enroll` | `Extrovert.enrolled` | `redeem_enrollment` |
| Check identity | `GET /v1/auth/me` | `whoami` | `whoami` |
| Create an inbox | `POST /v1/inboxes` | `inboxes.create` | `create_inbox` |
| List or get inboxes | `GET /v1/inboxes`, `GET /v1/inboxes/{id}` | `inboxes.list`, `inboxes.get` | `list_inboxes`, `get_inbox` |
| Send | `POST /v1/inboxes/{id}/send` | `inbox.send` | `send_email` |
| Reply | `POST /v1/inboxes/{id}/reply` | `inbox.reply` | `reply_email` |
| Forward | `POST /v1/inboxes/{id}/messages/{message_id}/forward` | `inbox.forward` | `forward_email` |
| List or get messages | `GET /v1/inboxes/{id}/messages`, `GET /v1/messages/{message_id}` | `inbox.messages`, `messages.get` | `read_messages`, `get_message` |
| Search messages | `GET /v1/inboxes/{id}/messages/search` | `inbox.search` | `search` |
| Update several messages | `PATCH /v1/inboxes/{id}/messages/batch` | `inbox.batchUpdateMessages` | `batch_update_messages` |
| List or get threads | `GET /v1/inboxes/{id}/threads`, `GET /v1/inboxes/{id}/threads/{thread_id}` | `inbox.threads`, `inbox.thread` | `list_threads`, `get_thread` |
| List or download attachments | `GET .../attachments`, `GET .../attachments/{attachment_id}` | `inbox.attachments`, `inbox.attachment` | `list_attachments`, `get_attachment` |
| Wait for a message | `POST /v1/inboxes/{id}/wait` | `inbox.waitForEmail` | `wait_for_email` |
| Manage webhooks | `/v1/webhooks` | `webhooks.*` | `register_webhook`, `update_webhook`, `list_webhooks`, `get_webhook`, `delete_webhook` |
| Manage contacts and suppression | `/v1/contacts`, `/v1/suppression` | See the SDK reference | `add_contact`, `list_contacts`, `remove_contact`, `check_suppression`, `list_suppressions`, `suppress_email`, `unsuppress_email` |

See the [API overview](https://docs.extrovert.dev/api/overview/) for request and response details. The current OpenAPI document
is available at [`/openapi.yaml`](https://api.extrovert.dev/openapi.yaml).

## TypeScript flow

```ts title="migration.ts"
import { Extrovert } from "@extrovert.dev/sdk";

const bootstrap = new Extrovert({
  apiKey: process.env.EXTROVERT_ENROLLMENT_KEY!,
});
const { client: extrovert, enrollment } = await bootstrap.enrolled({
  token: process.env.EXTROVERT_ENROLLMENT_KEY!,
  agent_handle: "support-bot",
});

const inbox = await extrovert.inboxes.create({
  display_name: "Support Bot",
  client_id: "support-bot-primary-inbox",
});

await inbox.send({
  to: "ops@acme.test",
  subject: "Status update",
  text: "The import is complete.",
  intent: { summary: "Tell operations that the requested import finished" },
  idempotency_key: "import-complete-2026-09-01",
});

const incoming = await inbox.waitForEmail({
  from: "ops@acme.test",
  timeout_seconds: 120,
});

const thread = await inbox.thread(incoming.message!.thread_id);
const latest = thread.messages.at(-1);
if (!latest) throw new Error("Thread has no messages");

await inbox.reply({
  thread_id: thread.id,
  expected_last_message_id: thread.last_message_id,
  text: "I will check it now.",
  intent: { summary: "Acknowledge the follow-up request" },
  idempotency_key: `reply-${latest.id}`,
});
```

This reads the oldest-first conversation before composing, then lets the server resolve the newest
reply parent from `thread_id` at submission. The optional expected-last-message guard returns `409` if
the conversation advanced between read and submit; it is not a delivery lock. Prefer each message's
`extracted_text` for concise context, but fall back to source `text` when extraction is null or exact
wording matters.

Store `enrollment.agent_key` after the first redemption. Enrollment keys create agents. Agent API
keys authenticate later calls.

## Differences to account for

### Outbound mail always has a review record

Every agent-plane `send`, `reply`, and `forward` creates a review record. New inboxes start with
`require_review`, so include an `intent` with every outbound request and expect
`202 queued_for_review`. Follow the returned review id until a terminal `sent`, `send_failed`, or
`cancelled` event arrives.

A human can change the inbox policy to `allow_direct`. Even then, Extrovert records the review and
returns its id. Read `effective_review_policy` before composing if your workflow needs to explain what
will happen.

See the [Review Loop contract](https://docs.extrovert.dev/review-loop/agent-contract/).

### Source and extracted message bodies are separate

`text` and `html` reflect the source MIME parts. Either may be absent. `extracted_text` and
`extracted_html` are derived views intended for reply cleanup and presentation. Extrovert does not
invent a text or HTML alternative that the message did not contain.

Extraction is best-effort and may omit signatures or quoted history. Keep the source fields when the
agent needs evidence, precise quoting, or a fallback for a null extracted alternative.

The MCP `get_message` tool can select `format: "auto" | "text" | "html" | "both"` and
`variant: "source" | "extracted"`. The structured message still keeps the four fields separate.

### Folders replace overlapping labels

A message has one folder and standard read state. Use folder filters, `mark_read`, full-text search,
contacts, and the suppression list for common triage and safety workflows. Extrovert does not expose
an overlapping per-message label graph.

### Hosted MCP replaces framework-specific adapters

Connect an MCP-capable host to `https://mcp.extrovert.dev/mcp` and sign in through OAuth. Hosted MCP
permissions follow the signed-in organization role. It also accepts an agent API key as a bearer
credential. Enrollment keys are not accepted by hosted MCP.

Use the [MCP setup guide](https://docs.extrovert.dev/mcp/client-configuration/) for configuration details.

## Features that do not map directly

| Agent Mail feature | Extrovert status |
|---|---|
| Per-message overlapping labels | Use folders, read state, and search. |
| Draft lifecycle and scheduled send | Not available. |
| Semantic search | Use full-text message and thread search. |

## Next

[Create an inbox](https://docs.extrovert.dev/quickstart/zero-to-first-email/)
  [Wait for email](https://docs.extrovert.dev/quickstart/wait-for-email/)
  [Authentication and permissions](https://docs.extrovert.dev/quickstart/authentication/)
  [Webhooks](https://docs.extrovert.dev/concepts/webhooks/)