---
title: "Email inboxes"
description: "Where a profile's confirmation codes arrive: bind an address per profile over IMAP or your own command, and let an agent read the code without opening a mailbox in the browser."
canonical: "https://scalebrowser.net/docs/inboxes"
---

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

# Email inboxes

> Where a profile's confirmation codes arrive: bind an address per profile over IMAP or your own command, and let an agent read the code without opening a mailbox in the browser.

After nearly every sign-up and half of all sign-ins comes the same step: "we sent you a code". This layer fetches it, with no browser tab involved.

## Why this exists, honestly
Not because an agent could not otherwise get the code. It can open a webmail provider in its own profile, click the message and read it. That path works.

The real reason sits one level earlier: **a mailbox in the browser presupposes a mailbox account, and one account per profile is the very wall this product is about.** A Google account wants a phone number, an Outlook account wants a check, and both are themselves a sign-up behind the same defences. An account several profiles share is a fleet link stronger than any fingerprint.

A catch-all domain gives you unlimited addresses without a single account ever existing.

Three smaller reasons come with it, none of them decisive on its own: no second login inside the profile, no extra tab and navigation in a flow that already waits, and throwaway mailboxes measurably do not work as a cheap way around the account.

<Note>

**The one thing that argues against this layer**, because nobody else writes it down: egress. A mailbox opened in the profile's own browser goes through the profile's proxy by itself. This layer had to rebuild that path, and it falls back to this machine's own connection where the browser path would not.

</Note>

## Two sources, one tool
| Source                | For                                                                                                       |
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
| **IMAP**, built in    | Email. Any receiving setup that speaks it: your own domain with a catch-all, Migadu, Purelymail, Mailcow, Zoho, a Gmail app password. |
| **Your own command**  | Everything else: SMS number services, provider APIs, a mailbox behind OAuth. Configured as `[mcp] inbox_command`. |

To the agent both are the same tool and the same reply shape. Whether a code arrived by mail or by text changes nothing about its work.

## Setting one up
A mailbox is a row on the daemon; a **binding** connects one profile to one address in it.

| Call                                       | What it does                                              |
| ------------------------------------------ | ----------------------------------------------------------- |
| `GET` · `POST /v1/inboxes`                 | List, create a mailbox.                                    |
| `PUT` · `DELETE /v1/inboxes/:id`           | Update, remove.                                            |
| `GET /v1/profiles/:id/inbox`               | This profile's bindings.                                   |
| `PUT /v1/profiles/:id/inbox`               | Bind an address or a number to this profile.               |
| `DELETE /v1/profiles/:id/inbox/:channel`   | Unbind one channel.                                        |

A mailbox row carries host, port, user, whether TLS starts at the first byte, the folder to read, and optionally an `address_domain`. That last field is what makes a catch-all useful: new addresses are minted under it, one per profile, so you never type an address by hand.

Leave it empty for a mailbox with exactly one address, and put that address in the binding instead.

<Warning>

**Plaintext IMAP is only accepted towards this machine.** Anything else requires TLS from the first byte.

The mailbox password is encrypted at rest with the daemon's master key. A row whose password will not decrypt is flagged `credentials_unreadable`: it stays listable and deletable, and every use of it fails closed. Same rule as a proxy.

</Warning>

## What an agent gets
Two tools, both in the `credentials` set. The desktop app serves it; on a daemon you run yourself it is opt-in. See [Credentials & logins](/docs/credentials).

**`read_inbox`** answers with recent messages for this profile's bound address:

| Argument           | What it does                                                                                          |
| ------------------ | -------------------------------------------------------------------------------------------------------- |
| `channel`          | `email` or `sms`. Default `email`.                                                                     |
| `after`            | Only messages at or after this Unix second. Defaults to the last ten minutes.                          |
| `timeout_ms`       | Keep looking until something arrives, up to 60 seconds. Without it the call answers immediately with whatever is already there. |
| `from_contains` · `subject_contains` | Narrow it.                                                                           |
| `limit`            | Up to 25.                                                                                              |

The reply carries each message's age, so an agent can tell a fresh code from one that was sitting there before it started. Pass the `next_after` of one call to the next to see only what is new.

**`inbox_open_link`** opens a link out of a message in the bound tab, by its number as `read_inbox` listed it.

<Note>

**The link address itself is never shown to the agent.** A sign-in link *is* the account: anyone holding it is signed in. So it is used and not read, and the agent picks by the link's text and host. The message is looked up again rather than remembered, so a window that no longer contains it answers that it is gone.

</Note>

## Nothing is stored
Messages are read and handed over. The daemon keeps no copy of a message body, no code, and no history of what an agent read.

## The catch-all rule that bites
A catch-all folder is not one profile's mailbox. It is **every** bound profile's, in one pile.

So a read there tells the server whose mail it wants, and re-checks locally on the way back. Without that, two profiles type each other's codes.

Two details of that are worth knowing, because both were real defects:

- **A refused search comes back as an empty result**, which looks exactly like "nothing matched". The layer settles it with a second search whose answer it already knows.
- **The server has to be asked the same question the client answers.** A recipient can appear in six different headers, and a search that names only `To` never returns mail that carries the address in `Cc`. Each side was right about its own half, and the gap between them was invisible from either.

Scoping runs only for a catch-all. A mailbox with one address does not need it.

## Next
- [Credentials & logins](/docs/credentials): the account the code belongs to.
- [Secrets](/docs/secrets): why a code that reaches a transcript is a problem.
- [MCP server](/docs/agents/mcp-server): enabling the `credentials` tool set.
