# Inbox quickstart

> Give an agent an email inbox in about five minutes: a key, an address and a sent message, no domain needed. The free address is waiting when you sign up.

An address is a resource. Mail arriving is an HTTP request. Everything here
works on a free account.

**You already have an address.** Signing up gives you one on our shared
domain, with a welcome message in it — no DNS, no card, nothing to wait for.
Use it for everything below.

A domain of your own is an upgrade rather than a first step: it is what makes
mail come *from* you, and it is [its own page](https://pidgeon.ai/docs/domains.md) when you want
it.

## Get a key

Keys are created in **Developers → API keys** inside the app. A key is shown
once; afterwards the list holds a prefix and nothing else, so store it where
you store the rest of your secrets.

```bash
export PIDGEON_API_KEY=pgn_live_…
```

```ts
// .env.local
PIDGEON_API_KEY=pgn_live_…
```

```python
# .env
PIDGEON_API_KEY=pgn_live_…
```

## Install a client

cURL needs nothing. The TypeScript client runs on Node 18 or later, and the
Python one on Python 3.11 or later; both are generated from the same API, so
an operation has the same name in each.

```bash
# Nothing to install.
```

```ts
npm install @pidgeon-ai/sdk
```

```python
pip install pidgeon
```

## Ask what you own

The shortest call that proves the key works. It lists the addresses on the
account, which is also how you find the id you will need later — including
the one signup gave you.

```bash
curl https://pidgeon.ai/api/v1/identities -H "Authorization: Bearer $PIDGEON_API_KEY"
```

```ts
import { client, identitiesList } from '@pidgeon-ai/sdk';

client.setConfig({ auth: () => process.env.PIDGEON_API_KEY! });

const { data } = await identitiesList();
```

```python
import os

from pidgeon import AuthenticatedClient
from pidgeon.api.identities import identities_list

client = AuthenticatedClient(
  base_url="https://pidgeon.ai/api/v1",
  token=os.environ["PIDGEON_API_KEY"],
)

page = identities_list.sync(client=client)
```

## Ask what is left before sending

Before the first send, ask what stands in the way. It checks everything a
send would — the key's level, test mode, the domain's DNS, the address, the
account's daily allowance — without sending, and lists the steps in order.

```bash
curl https://pidgeon.ai/api/v1/agent/setup -H "Authorization: Bearer $PIDGEON_API_KEY"
```

```ts
import { agentSetup } from '@pidgeon-ai/sdk';

const { data } = await agentSetup();
```

```python
from pidgeon.api.agent import agent_setup

setup = agent_setup.sync(client=client)
```

`ready_to_send` is true when nothing is left. Otherwise `next` is the first
step not done, and every step says who does it: `owner: "agent"` is your
code (create an address, wait out a limit until `resets_at`), and
`owner: "person"` is somebody with access to the account (publish the DNS
records the step lists, return a signup code, make a key that can send). `how`
says what to do, in words you can pass to that person. Add `identity_id` to
ask about one address; without it, the answer is about the address nearest
to sending, and `addresses` lists the rest. A key only hears about the
addresses it reaches.

## Send one

`from` has to be an address this account owns — the one signup gave you, or
one on a domain you have since verified. Everything
else is what you would expect.

```bash
curl https://pidgeon.ai/api/v1/messages -H "Authorization: Bearer $PIDGEON_API_KEY" -H "Content-Type: application/json" -d '{
  "from": "hello@yourdomain.com",
  "to": "someone@example.com",
  "subject": "First one",
  "text": "It works."
}'
```

```ts
await messagesSend({
body: {
  from: 'hello@yourdomain.com',
  to: 'someone@example.com',
  subject: 'First one',
  text: 'It works.',
},
});
```

```python
from pidgeon.api.messages import messages_send
from pidgeon.models import MessagesSendBody

messages_send.sync(
  client=client,
  body=MessagesSendBody(
      from_="hello@yourdomain.com",
      to="someone@example.com",
      subject="First one",
      text="It works.",
  ),
)
```

A `201` comes back with the message id and the thread it landed in. Recipients
that fail the send guard are returned in `rejected` rather than raised — one
bad address does not lose the other four.

## Then receive one

Sending is the half most APIs do. [Webhooks](https://pidgeon.ai/docs/webhooks.md) are the other
half, and they are the reason this product exists: mail arriving at `hello@yourdomain.com`&#x20;
becomes an HTTP request to your code within seconds, with nothing to poll.

That is the next page, and it is the one worth reading.
