# Quickstart

> A key, an address and a sent message, in about five minutes — no domain needed. The free address and the welcome mail are 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)
```

## 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.
