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

# Quickstart

> Create an inbox, send a message, and handle the reply — in about five minutes.

Everything below runs against the live service at `https://agent.waterr.ai`.

If you already have a Waterr `wai_` developer key, it works here as-is — no separate credential to provision. Otherwise see [Authentication](/agent-mailbox/authentication).

```bash theme={null}
export AGENT_MAILBOX_KEY="wai_live_..."   # or an aik_ mailbox key
export BASE="https://agent.waterr.ai"
```

<Steps>
  <Step title="Create an inbox">
    The `username` becomes the address. Leave it out and one is generated.

    ```bash theme={null}
    curl -X POST "$BASE/v0/inboxes" \
      -H "Authorization: Bearer $AGENT_MAILBOX_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "username": "ava",
        "display_name": "Ava from Support"
      }'
    ```

    ```json Response theme={null}
    {
      "inbox_id": "ava@agent.waterr.ai",
      "email": "ava@agent.waterr.ai",
      "display_name": "Ava from Support",
      "created_at": "2026-09-27T10:04:11.320Z",
      "updated_at": "2026-09-27T10:04:11.320Z"
    }
    ```

    The `inbox_id` is the address itself, and it is what you pass in every subsequent URL.
  </Step>

  <Step title="Send a message">
    ```bash theme={null}
    curl -X POST "$BASE/v0/inboxes/ava@agent.waterr.ai/messages/send" \
      -H "Authorization: Bearer $AGENT_MAILBOX_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "to": "customer@example.com",
        "subject": "Your refund for order 4471",
        "text": "Hi — the refund is processed. It should land in three working days."
      }'
    ```

    The response carries the stored `Message`, including the `thread_id` it was filed under. Hold onto that: it is how you follow up later without starting a new conversation.

    <Warning>
      If this is the very first contact with that recipient, see [Limits](/agent-mailbox/limits#cold-outbound-is-unreliable) first. Cold outbound is currently unreliable to major providers.
    </Warning>
  </Step>

  <Step title="Receive the reply">
    When the customer replies, the service parses and threads it, then fires a `message.received` event. Register a webhook to catch it:

    ```bash theme={null}
    curl -X POST "$BASE/v0/webhooks" \
      -H "Authorization: Bearer $AGENT_MAILBOX_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://your-app.example.com/hooks/mail",
        "event_types": ["message.received"]
      }'
    ```

    The response includes a `secret` — store it, it is shown once. Use it to verify the `X-AgentInbox-Signature` header on every delivery. See [Events](/agent-mailbox/events).
  </Step>

  <Step title="Reply on the thread">
    Reply to the message your webhook just received. Recipients are derived from the parent, so you do not pass `to`.

    ```bash theme={null}
    curl -X POST "$BASE/v0/inboxes/ava@agent.waterr.ai/messages/msg_4tq8rv2mzx7kd9nbhf3wpc6s/reply" \
      -H "Authorization: Bearer $AGENT_MAILBOX_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "text": "Checked again — it cleared this morning. You should see it now." }'
    ```

    You can reply as many times as the conversation needs, and you can send on the thread later without an inbound message to reply to.
  </Step>
</Steps>

## Giving your model the right text

Every stored message carries both the raw body and a cleaned one:

| Field            | Contents                                                                    |
| ---------------- | --------------------------------------------------------------------------- |
| `text`           | The full plain-text body as received, quoted history and signature included |
| `extracted_text` | Only what the person actually wrote this time                               |
| `preview`        | First \~200 characters of `extracted_text`                                  |

Feed `extracted_text` to your model. On a long thread, `text` is mostly a transcript of the conversation quoted back at you, which wastes context and invites the model to reply to a message from two weeks ago.

## A minimal agent loop

```javascript theme={null}
import express from "express";

const BASE = "https://agent.waterr.ai";
const KEY = process.env.AGENT_MAILBOX_KEY;

const api = (method, path, body) =>
  fetch(`${BASE}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${KEY}`,
      "Content-Type": "application/json",
    },
    body: body ? JSON.stringify(body) : undefined,
  }).then((r) => r.json());

const app = express();
app.use(express.json());

app.post("/hooks/mail", async (req, res) => {
  // Acknowledge immediately; the dispatcher retries anything non-2xx.
  res.sendStatus(200);

  const { event_type, message } = req.body;
  if (event_type !== "message.received") return;

  // Pull the whole thread so the model has the history.
  const thread = await api(
    "GET",
    `/v0/inboxes/${message.inbox_id}/threads/${message.thread_id}`,
  );

  const reply = await yourModel(thread.messages.map((m) => ({
    role: m.direction === "sent" ? "assistant" : "user",
    content: m.extracted_text,
  })));

  await api(
    "POST",
    `/v0/inboxes/${message.inbox_id}/messages/${message.message_id}/reply`,
    { text: reply },
  );
});

app.listen(3000);
```

Return `2xx` before you do the slow work. The dispatcher treats any other status as a failure and retries with backoff, so a handler that calls a model synchronously and takes 40 seconds will be retried while it is still thinking.

## Next

<CardGroup cols={2}>
  <Card title="Drafts" icon="pen-to-square" href="/agent-mailbox/drafts">
    Put a human in the loop before anything sends.
  </Card>

  <Card title="Events" icon="bolt" href="/agent-mailbox/events">
    Webhook signatures, retry behaviour, and the WebSocket alternative.
  </Card>
</CardGroup>
