# Quickstart

Add a domain, publish the DNS records, create an API key, and POST to /v1/emails. On a fresh account this takes under five minutes, and you can do the whole thing in test mode first without a verified domain.

## 1. Create a key

Keys are scoped, can be pinned to a single domain and an IP allowlist, and are shown exactly once. Start with a test key — test sends need no verified domain, consume no quota, and produce real webhook events on a realistic delay curve.

```bash
export MAILHAAP_API_KEY="mh_test_a1b2c3d4e5f6g7h8i9j0k1"
```

## 2. Send

```bash
curl -X POST https://api.mailhaap.com/v1/emails \
  -H "Authorization: Bearer $MAILHAAP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-1" \
  -d '{
    "from": "You <hello@yourdomain.com>",
    "to": ["delivered@sim.mailhaap.com"],
    "subject": "It works",
    "html": "<p>Hello from MailHaap</p>"
  }'
```

You get a 202 with a message id. The send itself happens in a worker — the API never blocks on the provider.

```json
{
  "id": "msg_01J8XQ7M2K9PQRSTUV",
  "status": "queued",
  "created_at": "2026-08-21T16:04:11Z"
}
```

## 3. Watch what happened

Every message carries a trace id that is queryable in the dashboard, so “why did this email not arrive” is answerable from the message id alone. The simulator addresses below produce each outcome deliberately, so you can prove your bounce and complaint handling works before a real bounce proves it does not.

| Address | Outcome |
|---|---|
| delivered@sim.mailhaap.com | Delivered after ~2 s |
| bounced@sim.mailhaap.com | Hard bounce, SMTP 550 |
| soft-bounced@sim.mailhaap.com | Soft bounce, then delivery on retry |
| complained@sim.mailhaap.com | Delivered, then a complaint after ~10 s |
| suppressed@sim.mailhaap.com | Rejected at accept with recipient_suppressed |
| slow@sim.mailhaap.com | Delivered after ~90 s |
| opened@sim.mailhaap.com | Delivered, then open and click events |

> **Test mode is not a sandbox with different behaviour** — A test key accepts identical requests against the same code path. The only difference is that nothing is delivered and nothing is billed. If it works in test mode, the live call is the same call with a different key.
