Skip to content

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.

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.

Terminal window
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.

Terminal window
export EXTROVERT_API_KEY="pk_agent_…"

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.

Terminal window
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" }'

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.

Terminal window
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" }
}'

The normal response under require_review is:

{
"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.

After the review reaches sent, wait for new mail:

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");

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 for the difference between source text and html fields and their best-effort extracted variants.

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

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",
});

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.