# Test inbox API

Catch test emails from any email provider and wait for them in your tests. Your team has a test domain, `<team>.test.groundmail.dev`. Mail sent to any address there is kept in your test inbox for 14 days and never delivered to anyone. Emails you send with a `gm_test_` key land here too.

For the walkthrough with Playwright, Vitest, Jest, Cypress and pytest, see [Email testing](https://docs.groundmail.dev/email-testing).

Base URL: `https://api.groundmail.dev`

Auth (any key with the `emails:read` scope, test keys included):

```http
Authorization: Bearer gm_...
```

| Endpoint | What it does |
|----------|--------------|
| `GET /test-inbox` | Your test domain, an example address and this month's usage |
| `GET /test-inbox/emails` | Caught emails, newest first, filtered by `to` and `since` |
| `GET /test-inbox/emails/{id}` | One email with bodies, headers and source |
| `GET /test-inbox/wait` | Long-poll until an email to `to` arrives (up to 60 seconds) |

Every email carries `code` and `links`, extracted for you:

- `code`: the one-time code in the email, 4 to 8 digits (`123456`, also written `123 456` or `123-456`), preferring one next to words like "code" or "verification". Years, prices, times and order numbers don't count. `null` when there is none.
- `links`: every `http(s)` link in the email, in order, without duplicates (the HTML's links, or the URLs in the text when there is no HTML).

---

## Get your test domain

`GET /test-inbox`

The first call creates your team's test domain. `address` is a fresh example address on every call.

```bash
curl https://api.groundmail.dev/test-inbox \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY"
```

```json
{
  "test_inbox": {
    "object": "test_inbox",
    "domain": "acme.test.groundmail.dev",
    "address": "run-k3m9x2pq@acme.test.groundmail.dev",
    "addon": { "active": false, "status": null },
    "usage": {
      "month": "2026-10",
      "received": 42,
      "rejected": 0,
      "included": 100,
      "overage": false,
      "resets_at": "2026-11-01T00:00:00.000Z"
    },
    "retention_days": 14
  }
}
```

`usage.included` is 100 a month on every account, or 5,000 with the email testing add-on. Without the add-on, test emails past the limit are refused (`usage.rejected`) until `resets_at`. With it, they are accepted and billed per 1,000 (`usage.overage: true`). Emails sent with a `gm_test_` key are free and don't count.

---

## List test emails

`GET /test-inbox/emails`

| Query | Description |
|-------|-------------|
| `to` | Only emails to this exact address |
| `since` | Only emails received at or after this time (ISO 8601) |
| `limit` | 1–100, default 25 |
| `cursor` | `next_cursor` from the previous page |

```bash
curl "https://api.groundmail.dev/test-inbox/emails?to=signup-k3m9x2pq@acme.test.groundmail.dev" \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY"
```

```json
{
  "emails": [
    {
      "object": "test_inbox_email",
      "id": "tie_3f9c2a1b7d8e4f6a0b1c2d3e",
      "source": "inbound",
      "email_id": null,
      "from": "Acme <no-reply@acme.com>",
      "to": ["signup-k3m9x2pq@acme.test.groundmail.dev"],
      "cc": [],
      "subject": "Your Acme verification code",
      "code": "482913",
      "links": ["https://app.acme.com/verify?token=abc"],
      "received_at": "2026-10-22T10:00:00.000Z",
      "expires_at": "2026-11-05T10:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

`source` is `inbound` for mail received at your test domain, and `test_key` for an email you sent through Groundmail with a `gm_test_` key (`email_id` is then that email's id).

---

## Retrieve a test email

`GET /test-inbox/emails/{id}`

Returns `{ "email": … }`: the list fields plus `text_body`, `html_body`, `headers` (`[{ "name", "value" }]` in message order) and `raw_source` (the raw message, when available).

```bash
curl https://api.groundmail.dev/test-inbox/emails/tie_3f9c2a1b7d8e4f6a0b1c2d3e \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY"
```

`404 not_found` when there is no such email for your team (or it expired).

---

## Wait for a test email

`GET /test-inbox/wait`

Holds the request open until an email to `to` arrives, then returns it like `GET /test-inbox/emails/{id}`. An email that already arrived is returned at once, so it doesn't matter whether your test calls this before or after your app sends.

| Query | Description |
|-------|-------------|
| `to` | Required. The exact address your app sent to |
| `since` | Only emails received at or after this time (ISO 8601). Useful when one address receives several emails |
| `timeout` | Seconds to wait, default 30, at most 60 |

```bash
curl "https://api.groundmail.dev/test-inbox/wait?to=signup-k3m9x2pq@acme.test.groundmail.dev&timeout=30" \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY"
```

When several emails match, the newest is returned. If nothing arrives in time, the response is `408` with code `timeout`:

```json
{
  "error": {
    "message": "No email to signup-k3m9x2pq@acme.test.groundmail.dev arrived within 30 seconds",
    "code": "timeout",
    "hint": "Nothing arrived in time. Check that your app sent the email to the exact address you are waiting on, then call again: the wait is safe to repeat and returns an email that arrived in the meantime. Waits last at most 60 seconds per call.",
    "docs_url": "https://docs.groundmail.dev/errors#timeout",
    "details": { "to": "signup-k3m9x2pq@acme.test.groundmail.dev", "timeout": 30 }
  }
}
```

Calling again is safe. The SDK's `testInbox.waitFor` repeats the call for timeouts longer than 60 seconds.

---

## SDK, CLI and MCP

```ts
import { Groundmail } from 'groundmail'

const groundmail = new Groundmail(process.env.GROUNDMAIL_API_KEY!)

const to = await groundmail.testInbox.address({ prefix: 'signup' }) // signup-k3m9x2pq@acme.test.groundmail.dev
const email = await groundmail.testInbox.waitFor({ to, timeout: 30_000 })
console.log(email.code, email.links)

await groundmail.testInbox.list({ to })
await groundmail.testInbox.get(email.id)
await groundmail.testInbox.info() // domain and usage
```

```bash
TO=$(groundmail test-inbox address --prefix signup)
groundmail test-inbox wait --to "$TO" --timeout 30
groundmail test-inbox list --to "$TO"
```

MCP tools: `get_test_inbox_address`, `wait_for_test_email`, `list_test_emails`, `get_test_email`.
