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

# Quickstart

> From no key to reading mail in about two minutes, then four things integrations actually do.

## 1. Make a key

**Settings → API keys → Create key.** Give it a name you will recognise in six
months, then choose permissions.

Give it the narrowest set that does the job. If it leaks, that set is the whole
blast radius — a key that only reads the inbox cannot also send mail from your
domain. For this walkthrough: **Mailboxes → Read** and **Messages → Read**.

The key is shown once, starting `rk_`. Copy it now.

## 2. Find out what you own

Every other endpoint takes an address, and this is where the valid ones come
from.

```bash theme={null}
curl https://app.ruber.me/api/v1/mailboxes \
  -H "Authorization: Bearer $RUBER_API_KEY"
```

```json theme={null}
{
  "data": [
    { "address": "you@yourdomain.com", "mode": "smart", "status": "active", … }
  ]
}
```

If this returns `401`, the key is wrong or revoked. If it returns `403`, the key
is real but lacks `mailboxes:read` — the error names the scope it wanted.

<Note>
  **Check `mode` before anything else.** A mailbox with `"mode": "private"` is
  encrypted to a key only its owner's browser holds. It is listed so you know it
  exists, and every other endpoint will refuse it — there is nothing the API could
  return but ciphertext.
</Note>

## 3. Read the inbox

```bash theme={null}
curl "https://app.ruber.me/api/v1/messages?mailbox=you@yourdomain.com&limit=10" \
  -H "Authorization: Bearer $RUBER_API_KEY"
```

That is the whole quickstart. Everything below is a real task.

***

## Recipe: react to new mail

**Do not poll for this.** Register a webhook for `message.received` and Ruber
calls you the moment mail is delivered — see [Webhooks](/api/webhooks).

Polling `GET /v1/messages` on a timer is the obvious approach and the wrong one:
it is a fixed cost whether or not anything arrived, it is always at least one
interval behind, and it opens an IMAP session on the mail server every tick.

If you genuinely cannot receive a webhook — no public URL, say — poll the inbox
with `filter=unread` and track the newest `received_at` you have already
handled. Do not use `uid` as a high-water mark: uids are per folder and a
mailbox rebuild can reset the range.

## Recipe: send a reply that threads properly

Read the message you are answering, then send with its `message_id`:

```bash theme={null}
# 1. the message you are replying to
curl "https://app.ruber.me/api/v1/messages/55?mailbox=you@yourdomain.com" \
  -H "Authorization: Bearer $RUBER_API_KEY"

# 2. the reply
curl -X POST https://app.ruber.me/api/v1/messages \
  -H "Authorization: Bearer $RUBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@yourdomain.com",
    "to": ["them@example.com"],
    "subject": "Re: Invoice 42",
    "text": "Paid this morning.",
    "in_reply_to": "<abc123@supplier.com>",
    "references": ["<abc123@supplier.com>"]
  }'
```

Without `in_reply_to` and `references` the reply is delivered correctly and
appears as a brand new conversation in the recipient's client — which is the
kind of wrong that nobody reports and everybody notices.

Needs **Sending → Write**.

## Recipe: archive a whole conversation

Threads hand you exactly what the bulk endpoint wants:

```bash theme={null}
# every conversation in the inbox
curl "https://app.ruber.me/api/v1/threads?mailbox=you@yourdomain.com" \
  -H "Authorization: Bearer $RUBER_API_KEY"

# archive one, using its uids
curl -X PATCH https://app.ruber.me/api/v1/messages \
  -H "Authorization: Bearer $RUBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mailbox": "you@yourdomain.com",
    "folder": "inbox",
    "uids": [42, 43, 47],
    "read": true,
    "move_to": "archive"
  }'
```

One request, not three. Needs **Messages → Write**.

## Recipe: draft a reply for a human to approve

The shape most teams actually want from an assistant: write it, do not send it.

```bash theme={null}
curl -X POST https://app.ruber.me/api/v1/drafts \
  -H "Authorization: Bearer $RUBER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["them@example.com"],
    "subject": "Re: Invoice 42",
    "text": "Draft for review."
  }'
```

It appears in the Drafts folder in the web app, where somebody opens it in the
composer, edits it and presses send. Needs **Drafts → Write**.

***

## Four things worth knowing before you build

**A `uid` is not a permanent id.** It identifies a message inside **one folder**,
and moving a message gives it a new one. Store `message_id` — the sender's — if
you need something durable.

**Reading a message does not mark it read.** That is a separate call, so an
integration that indexes a mailbox does not silently change what its owner sees
as new.

**Sending is its own permission.** `messages:write` files and stars mail that
already exists; it does not let a key send. That separation is the point.

**`202` from a send means accepted, not delivered.** Whether it arrived is
decided minutes later by a server nobody here controls. Subscribe to
`message.bounced` if you need to know.

## Where to go next

<CardGroup cols={2}>
  <Card title="Messages" href="/api/messages">
    List, read, and change mail — including up to 500 at once.
  </Card>

  <Card title="Sending" href="/api/sending">
    Send, with attachments, threading and the limits that apply.
  </Card>

  <Card title="Webhooks" href="/api/webhooks">
    Be told when mail arrives, sends or bounces.
  </Card>

  <Card title="How it works" href="/api/introduction">
    Permissions, errors, pagination and versioning.
  </Card>
</CardGroup>
