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

# Send your first email

> Install an SDK or the CLI, verify a domain, and send a real email from your own address — then track its delivery and read the reply.

Send a real email from an address on your own domain, watch it deliver, and
read the reply — using an SDK, the CLI, or plain `curl`. Once your domain is
verified, the send itself takes about two minutes.

## Before you start

You need:

* A Dairo account and an [API key](/get-started/authentication).
* A domain you control, so you can publish DNS records to verify it.

Every step has a CLI tab, so you can complete the whole walkthrough from a
terminal without writing code.

## 1. Install

<CodeGroup>
  ```bash title="npm" theme={null}
  npm install dairo
  ```

  ```bash title="pip" theme={null}
  pip install dairo
  ```

  ```bash title="Go" theme={null}
  go get github.com/dairo-app/dairo-go
  ```

  ```bash title="CLI" theme={null}
  curl -fsSL https://dairo.app/install.sh | sh
  ```
</CodeGroup>

The [SDKs page](/sdks/overview) covers Ruby, PHP, Java, Rust, .NET, and Elixir.

## 2. Set your API key

Store your key in an environment variable so it never lands in source control.

```bash theme={null}
export DAIRO_API_KEY="dairo_live_..."
```

The Python SDK and the CLI read `DAIRO_API_KEY` from the environment on their
own. The JavaScript SDK — and the other SDKs — take the key explicitly, so
browser and edge runtimes stay predictable.

## 3. Verify a domain

Register your domain and Dairo returns the exact DNS records to publish. Once
they resolve, your mail authenticates and lands in the inbox.

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  import { Dairo } from "dairo";

  const dairo = new Dairo({ apiKey: process.env.DAIRO_API_KEY! });

  const domain = await dairo.domains.create({ domain: "yourapp.com" });
  for (const record of domain.records) {
    console.log(record.type, record.host, "→", record.value);
  }
  ```

  ```python title="Python" theme={null}
  from dairo import Dairo

  dairo = Dairo()  # reads DAIRO_API_KEY

  domain = dairo.domains.create(domain="yourapp.com")
  for record in domain.records:
      print(record.type, record.host, "→", record.value)
  ```

  ```bash title="CLI" theme={null}
  dairo domain add yourapp.com
  ```

  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/domains \
    -H "Authorization: Bearer $DAIRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"domain":"yourapp.com"}'
  ```
</CodeGroup>

Publish the returned records at your DNS provider, then re-check until the
domain's `status` is `verified`:

<CodeGroup>
  ```bash title="CLI" theme={null}
  dairo domain recheck yourapp.com
  ```

  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/domains/yourapp.com/verify \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```
</CodeGroup>

<Note>
  DNS changes can take a few minutes to propagate; each re-check reads your
  records fresh. [Add & verify a domain](/domains/domains) has the full
  reference, including what each record does.
</Note>

## 4. Create an inbox

An inbox is the address you send from and receive replies to.

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  const inbox = await dairo.inboxes.create({
    username: "hello",
    domain: "yourapp.com",
  });
  console.log(inbox.id, inbox.address); // <id> hello@yourapp.com
  ```

  ```python title="Python" theme={null}
  inbox = dairo.inboxes.create(username="hello", domain="yourapp.com")
  print(inbox.id, inbox.address)  # <id> hello@yourapp.com
  ```

  ```bash title="CLI" theme={null}
  dairo inbox create hello --domain yourapp.com
  ```

  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/inboxes \
    -H "Authorization: Bearer $DAIRO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"username":"hello","domain":"yourapp.com"}'
  ```
</CodeGroup>

Keep the returned `inbox.id` — every send names it.

## 5. Send an email

One call sends a real email. Pass an `Idempotency-Key` so a retried request
never delivers twice — see [Idempotency](/concepts/idempotency).

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  const result = await dairo.messages.send(
    {
      inboxId: inbox.id,
      to: "you@example.com",
      subject: "Hello from Dairo",
      text: "Dairo is sending real email now.",
      html: "<p>Dairo is sending <strong>real email</strong> now.</p>",
    },
    { idempotencyKey: "quickstart-first-send" },
  );

  console.log(result.id, result.status, result.channel); // <id> sent email
  ```

  ```python title="Python" theme={null}
  result = dairo.messages.send(
      inbox_id=inbox.id,
      to="you@example.com",
      subject="Hello from Dairo",
      text="Dairo is sending real email now.",
      html="<p>Dairo is sending <strong>real email</strong> now.</p>",
      idempotency_key="quickstart-first-send",
  )

  print(result.id, result.status)  # <id> sent
  ```

  ```bash title="CLI" theme={null}
  dairo send \
    --inbox-id inbox_123 \
    --to you@example.com \
    --subject "Hello from Dairo" \
    --text "Dairo is sending real email now."
  ```

  ```bash title="cURL" theme={null}
  curl -X POST https://api.dairo.app/v1/messages \
    -H "Authorization: Bearer $DAIRO_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: quickstart-first-send" \
    -d '{
      "inboxId": "inbox_123",
      "to": "you@example.com",
      "subject": "Hello from Dairo",
      "text": "Dairo is sending real email now."
    }'
  ```
</CodeGroup>

An immediate send is performed while you wait: the response carries the
message `id`, the `channel` it went out on (`email`), and a definitive
`status` of `sent` — or a precise error if something knowable at submit time
is wrong. Keep the `id`; it links this send to its delivery, bounce, and
complaint events.

<Note>
  If a recipient previously reported your mail as spam, Dairo blocks the send
  with a `400` unless you pass `ignoreComplaints: true` — the response then
  carries a `warnings` entry for that recipient. See
  [Land in the inbox](/webhooks/deliverability) before overriding.
</Note>

<Tip>
  To send later, add `sendAt` — an RFC 3339 timestamp with an explicit timezone
  offset, up to 30 days ahead (for example `"2026-08-01T09:00:00Z"`), or
  `--send-at` on the CLI. The response comes back `scheduled`, and you can cancel
  the send any time before it fires.
</Tip>

## 6. Track delivery

Delivery outcomes arrive as events on the message: `Delivery` when the
recipient's provider accepts it, `Bounce` when it can't be delivered,
`Complaint` when someone marks it as spam.

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  const { events } = await dairo.messages.listEvents(result.id);
  for (const event of events) {
    console.log(event.type, event.recipient, event.occurredAt);
  }
  ```

  ```python title="Python" theme={null}
  for event in dairo.messages.list_events(result.id):
      print(event.type, event.recipient, event.occurred_at)
  ```

  ```bash title="CLI" theme={null}
  dairo outbound get <id>      # status and latest event
  dairo outbound events <id>   # the full delivery timeline
  ```
</CodeGroup>

For real-time updates, register a webhook instead of polling —
`message.delivered`, `message.bounced`, and `message.complained` fire as each
outcome lands.

## 7. Read the reply

Reply to the email you sent, from your regular mail client. Incoming mail
becomes a structured message in the inbox, grouped into a thread with the
message it answers.

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  const page = await dairo.messages.list({ inboxId: inbox.id });
  for (const m of page.messages) {
    console.log(m.direction, m.from.address, m.subject);
  }
  ```

  ```python title="Python" theme={null}
  page = dairo.messages.list(inbox_id=inbox.id)
  for m in page.messages:
      print(m.direction, m.from_.address, m.subject)
  ```

  ```bash title="CLI" theme={null}
  dairo messages list --inbox-id inbox_123
  ```
</CodeGroup>

## Next steps

* [Send an email](/sending/sending-email) — HTML, React templates, attachments,
  CC/BCC, and scheduling.
* [Messages & threads](/receiving/messages-and-threads) — read incoming mail,
  walk threads, and reply.
* [Webhooks](/webhooks/webhooks) — react to events in real time and verify
  signatures.
* [For AI agents](/agent-first/agent-patterns) — the MCP server, the CLI, and
  patterns for reliable email agents.
