# Zero to first email

This guide starts with an enrollment key because it is the normal bootstrap path while free self-signup
is paused. You will redeem the key, create an inbox, submit a message for review, wait for a reply, and
answer in the same thread.

## 1. Redeem an enrollment key

In the console, open **Credentials > Enrollment keys** and issue a key when setup needs to create the
agent. Select only the permissions the agent needs. For this guide, include `mailbox:create`,
`mailbox:read`, and `mailbox:send`.
**Enrollment key or API key?:** Use an enrollment key to create a new agent during setup. Use an API key when the agent already
  exists. Both secrets are shown once.

```bash frame="terminal"
export EXTROVERT_API_BASE_URL="https://api.extrovert.dev"
export EXTROVERT_ENROLLMENT_KEY="pk_enroll_…"

curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/enroll" \
  -H "Content-Type: application/json" \
  -d "{\"token\":\"$EXTROVERT_ENROLLMENT_KEY\",\"agent_handle\":\"support-agent\"}"
```

Store the returned `agent_key`; it is not shown again.

```bash frame="terminal"
export EXTROVERT_API_KEY="pk_agent_…"
```

  ```bash frame="terminal"
npm install @extrovert.dev/sdk@next
```

```ts title="enroll.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-agent",
});

console.log(enrollment.agent_key); // shown once
```

  ```json title="redeem_enrollment"
{
  "name": "redeem_enrollment",
  "arguments": {
    "enrollment_token": "pk_enroll_…",
    "agent_handle": "support-agent"
  }
}
```

The packaged local MCP server stores the returned agent key in its permission-restricted credential
file.

  ## 2. Create an inbox

Omit `username` and `domain` to create an `@extrovertmail.com` address on a paid account. Use a
stable `client_id` or `Idempotency-Key` when retrying after a transport timeout.

```bash frame="terminal"
curl -sS -X POST "$EXTROVERT_API_BASE_URL/v1/inboxes" \
  -H "Authorization: Bearer $EXTROVERT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: support-agent-primary" \
  -d '{ "display_name": "Support agent" }'
```

  ```ts title="create.ts"
const inbox = await extrovert.inboxes.create({
  display_name: "Support agent",
  client_id: "support-agent-primary",
});
console.log(inbox.id, inbox.address);
```

  ```json title="create_inbox"
{
  "name": "create_inbox",
  "arguments": {
    "display_name": "Support agent",
    "client_id": "support-agent-primary"
  }
}
```

  ## 3. Read the review policy

Call `GET /v1/inboxes/{inbox_id}`, `extrovert.inboxes.get(id)`, or `get_inbox` before the first send.
The single-inbox response includes `effective_review_policy`.

New accounts use `require_review`. Under that policy, every send, reply, and forward needs an
`intent.summary`. Without it, the API returns `422 intent_required`; nothing is sent or queued.

## 4. Submit a message

```bash frame="terminal"
curl -sS -X POST \
  "$EXTROVERT_API_BASE_URL/v1/inboxes/$INBOX_ID/send" \
  -H "Authorization: Bearer $EXTROVERT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: notify-ops-001" \
  -d '{
    "to": ["ops@acme.test"],
    "subject": "Support inbox ready",
    "text": "The support inbox is ready.",
    "intent": { "summary": "Tell operations that the support inbox is ready" }
  }'
```

  ```ts title="send.ts"
const outcome = await inbox.send({
  to: "ops@acme.test",
  subject: "Support inbox ready",
  text: "The support inbox is ready.",
  intent: { summary: "Tell operations that the support inbox is ready" },
  idempotency_key: "notify-ops-001",
});
```

  ```json title="send_email"
{
  "name": "send_email",
  "arguments": {
    "inbox": "pmbx_…",
    "to": ["ops@acme.test"],
    "subject": "Support inbox ready",
    "text": "The support inbox is ready.",
    "intent": { "summary": "Tell operations that the support inbox is ready" },
    "client_id": "notify-ops-001"
  }
}
```

  The normal response under `require_review` is:

```json
{
  "kind": "queued_for_review",
  "review": { "id": "rr_8Tz4kP", "state": "needs_review", "effective_mode": "review" }
}
```

The message has not been delivered. Wait for a terminal review event or poll
`GET /v1/reviews/rr_8Tz4kP` until `closed` is true.

## 5. Wait for a reply

After the review reaches `sent`, wait for new mail:

```ts title="wait.ts"
const result = await inbox.waitForEmail({
  from: "ops@acme.test",
  timeout_seconds: 120,
});

if (result.timed_out) throw new Error("No reply arrived in time");
```

  ```json title="wait_for_email"
{
  "name": "wait_for_email",
  "arguments": {
    "inbox": "pmbx_…",
    "from": "ops@acme.test",
    "timeout_ms": 120000
  }
}
```

  ```bash frame="terminal"
curl -sS -X POST \
  "$EXTROVERT_API_BASE_URL/v1/inboxes/$INBOX_ID/wait" \
  -H "Authorization: Bearer $EXTROVERT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "from": "ops@acme.test", "timeout_seconds": 120 }'
```

  `wait_for_email` returns the canonical message fields. It also returns an extracted OTP code and
verification link when present. See [Messages, threads, and search](https://docs.extrovert.dev/concepts/messages-and-threads/)
for the difference between source `text` and `html` fields and their best-effort extracted variants.

## 6. Reply in the same thread

Reference the returned `message_id`. The server derives recipients and threading headers. Reply follows
the same review policy as send.

```ts title="reply.ts"
await inbox.reply({
  message_id: result.message!.id,
  text: "Thanks. I will keep this address monitored.",
  intent: { summary: "Acknowledge the reply and confirm the inbox remains monitored" },
  idempotency_key: "reply-ops-001",
});
```

## Optional self-signup path

`GET /v1/signup-status` currently reports that free self-signup is paused. When enabled,
`POST /v1/agent/sign-up` creates the agent and an `@free.extrovertmail.com` inbox, then returns a
temporary key with only `signup:verify`. That key can only verify the emailed OTP. It cannot create,
read, send, or export credentials. `POST /v1/agent/verify` revokes it and returns the durable key, the
activated inbox address, and copy-ready MCP calls for reading and waiting.

The verified human can then sign in to the console with the same verified email address. Extrovert
shows the matching agent-created account and asks the human to claim it. Claiming makes that person the
owner without exposing a claim token in the browser flow.

## Next

- [Authentication and keys](https://docs.extrovert.dev/quickstart/authentication/)
- [`wait_for_email`](https://docs.extrovert.dev/quickstart/wait-for-email/)
- [Review Loop agent contract](https://docs.extrovert.dev/review-loop/agent-contract/)
- [Messages, threads, and search](https://docs.extrovert.dev/concepts/messages-and-threads/)