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

# inboxes

> DevHelm CLI inboxes commands: create webhook testing inboxes, wait for captured requests, and list, read, or download events

Create webhook testing inboxes, wait for the requests your code sends to them, and read or download what was captured. For how inboxes work, see [Webhook inboxes](/testing/webhooks/overview).

<Note>
  An inbox **receives** requests so you can assert on them. `devhelm webhooks` manages the opposite direction: endpoints DevHelm sends events to. See [`webhooks`](/cli/commands/webhooks).
</Note>

## Setup

The testing API needs an API key and a workspace ID. Export the key, look up the workspace, then export its ID:

```bash theme={null}
export DEVHELM_API_TOKEN=dh_live_xxxxxxxx
curl https://api.devhelm.io/api/v1/workspaces -H "Authorization: Bearer $DEVHELM_API_TOKEN"
export DEVHELM_WORKSPACE_ID=<workspace-id>
```

The CLI and Python SDK read both variables. The TypeScript SDK reads `DEVHELM_WORKSPACE_ID` and takes the key in its constructor. With curl, send the workspace as a header:

```bash theme={null}
-H "Authorization: Bearer $DEVHELM_API_TOKEN" \
-H "x-phelm-workspace-id: $DEVHELM_WORKSPACE_ID"
```

`--api-token` and `--api-url` override `DEVHELM_API_TOKEN` and `DEVHELM_API_URL` per command. There is no workspace flag. Without `DEVHELM_WORKSPACE_ID` the CLI sends workspace `1` and fails with 404 `Workspace doesn't exist`.

Every command takes `-o table|json|yaml` (default `table`) and `-v`. JSON and YAML print the bare object, without the API's `data` envelope. Inbox and event IDs are UUIDs; the CLI rejects other values before calling the API.

## Commands

| Command | Description |
| - | - |
| `devhelm inboxes list` | List inboxes in the workspace |
| `devhelm inboxes get <id>` | Get one inbox |
| `devhelm inboxes create` | Create an inbox and print its URL |
| `devhelm inboxes update <id>` | Rename, enable or disable, or change settings |
| `devhelm inboxes delete <id>` | Delete an inbox and its events |
| `devhelm inboxes activity` | Request counts for the last 24 hours |
| `devhelm inboxes wait <id>` | Wait for a matching request |
| `devhelm inboxes events list <id>` | List captured requests |
| `devhelm inboxes events get <id> <eventId>` | Get one captured request, with its body |
| `devhelm inboxes events raw <id> <eventId>` | Download the exact body bytes |
| `devhelm inboxes events clear <id>` | Delete every captured request on an inbox |

## inboxes list

List inboxes, newest first. The CLI prints the first page of 50. The table shows `ID`, `NAME`, `STATUS`, and `URL`.

```bash CLI theme={null}
devhelm inboxes list --search stripe
```

| Flag | Type | Description |
| - | - | - |
| `--search` | string | Case-insensitive match on the inbox name |

## inboxes get

```bash CLI theme={null}
devhelm inboxes get <inbox-id> -o json
```

The JSON includes `httpUrl`, `publicToken`, `status`, `httpResponse` (the mock reply), `cors`, `retentionDays`, `maxEvents`, and `lastEventAt`.

## inboxes create

Create an inbox. Table output prints only the capture URL; use `-o json` to get the ID as well.

```bash CLI theme={null}
devhelm inboxes create --name ci-orders -o json
```

```bash CLI theme={null}
INBOX_ID=$(devhelm inboxes create --name "ci-$GITHUB_RUN_ID" -o json | jq -r .id)
```

| Flag | Type | Required | Description |
| - | - | - | - |
| `--name` | string | Yes | Inbox name (max 200 characters, not unique) |
| `--status` | string | — | `active` (default) or `disabled` |
| `--cors` / `--no-cors` | boolean | — | Add `Access-Control-Allow-Origin: *` to replies (default on) |
| `--retention-days` | integer | — | Retention setting, up to the plan ceiling (the default). See [Security and data](/testing/security) |
| `--max-events` | integer | — | Events kept per inbox, 1–100000 (default `10000`). The oldest are dropped first |
| `--response-status` | integer | — | Mock reply status, 200–599 (default `200`) |
| `--response-body` | string | — | Mock reply body, up to 65,536 UTF-8 bytes (default empty) |
| `--response-content-type` | string | — | Mock reply `Content-Type` (default `text/plain`) |
| `--response-delay-ms` | integer | — | Delay before the reply, 0–30000 |
| `--response-header` | string | — | Reply header as `Name: value`. Repeatable, up to 32 |

```bash CLI theme={null}
devhelm inboxes create --name retry-test \
  --response-status 503 \
  --response-body '{"error":"unavailable"}' \
  --response-content-type application/json \
  --response-header "Retry-After: 2"
```

Reply header names must match `[A-Za-z][A-Za-z0-9-]{0,63}`. Credential and hop-by-hop names such as `Set-Cookie`, `Authorization`, and `Content-Length` are refused. `X-Event-Id` and `X-Inbox-Id` are always added. See [Simulate a failing receiver](/testing/webhooks/simulate-failures).

At the plan's inbox cap, create returns 403 `INBOUND_WEBHOOK_INBOX_LIMIT_REACHED`. The cap counts every inbox in the organization.

## inboxes update

```bash CLI theme={null}
devhelm inboxes update <inbox-id> --name ci-orders-v2 --max-events 500
```

| Flag | Type | Description |
| - | - | - |
| `--name` | string | New name |
| `--status` | string | `active` or `disabled`. A disabled inbox answers senders with 404 and stores nothing |
| `--cors` / `--no-cors` | boolean | Turn CORS headers on or off |
| `--retention-days` | integer | New retention setting, up to the plan ceiling |
| `--max-events` | integer | New event cap, 1–100000 |
| `--response-header` | string | Reply header as `Name: value`. Repeatable. **Replaces every configured reply header** |

`update` has no `--response-status`, `--response-body`, `--response-content-type`, or `--response-delay-ms`. Change those through the SDK or the API. A PATCH merges `httpResponse` field by field, except `headers`, which it replaces:

```bash API theme={null}
curl -X PATCH https://api.devhelm.io/api/v1/webhook/inboxes/<inbox-id> \
  -H "Authorization: Bearer $DEVHELM_API_TOKEN" \
  -H "x-phelm-workspace-id: $DEVHELM_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{"httpResponse": {"status": 200, "delayMs": 0}}'
```

## inboxes delete

Delete the inbox and every event on it. The capture URL returns 404 at once. A new inbox gets a new URL; tokens cannot be rotated.

```bash CLI theme={null}
devhelm inboxes delete <inbox-id> --yes
```

Pass `--yes` (`-y`) in scripts and CI. Without it the CLI prompts, and a non-interactive run is refused.

## inboxes activity

Request counts in 24 hourly UTC buckets. The table shows the total per inbox; JSON has the buckets.

```bash CLI theme={null}
devhelm inboxes activity --id <inbox-id> --id <other-inbox-id> -o json
```

| Flag | Type | Required | Description |
| - | - | - | - |
| `--id` | string | Yes | Inbox ID. Repeat for up to 100 inboxes |

Counts include requests that were later deleted, and the current partial hour.

## inboxes wait

Block until a matching request has been captured, then print it. JSON and YAML include the full body.

```bash CLI theme={null}
SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -s -X POST "$INBOX_URL/orders/42" -H "Content-Type: application/json" -d '{"status":"paid"}'
devhelm inboxes wait <inbox-id> --received-after "$SINCE" --path-prefix /orders --timeout-ms 20000 -o json
```

| Flag | Type | Default | Description |
| - | - | - | - |
| `--received-after` | string | local clock at call time | ISO 8601 timestamp. Only requests received at or after it match |
| `--timeout-ms` | integer | `30000` | How long to wait. Values above `120000` are clamped |
| `--method` | string | — | HTTP method to match. Without it, `OPTIONS` requests are skipped |
| `--path-prefix` | string | — | Case-sensitive prefix on the path after the token |

<Warning>
  Always pass `--received-after` with a time taken **before** you trigger the send. The default is the local clock when `wait` starts, so a request that arrived first is skipped and the wait times out.
</Warning>

* Wait returns the **oldest** match. It does not consume it: the same call returns the same request again.
* For the next request, pass the previous `receivedAt` plus 1 ms. Passing `receivedAt` unchanged returns the same request, because the match is inclusive.
* With no match before the timeout, the CLI prints `No matching inbound item arrived before timeout (inbox <id>)` and exits with code `11`. A disabled inbox times out the same way.
* All waits in the organization share 40 requests per minute, across inboxes, email, and every workspace. Over the limit returns 429.

See [Wait for a webhook in a test](/testing/webhooks/assert-in-tests) for test patterns and negative assertions.

## inboxes events list

List captured requests, newest first. The table shows `ID`, `METHOD`, `PATH`, and `RECEIVED`.

```bash CLI theme={null}
devhelm inboxes events list <inbox-id> --method POST --path /orders --limit 20
```

| Flag | Type | Description |
| - | - | - |
| `--method` | string | Exact method, for example `POST`. The list includes `OPTIONS` requests |
| `--path` | string | Case-sensitive **substring** of the path (not a prefix) |
| `--limit` | integer | Page size, 1–100 (default `50`) |
| `--cursor` | string | Cursor from a previous page |

The CLI prints the rows of one page and not the next cursor. Page through with the SDK or `GET /api/v1/webhook/inboxes/<inbox-id>/events`.

Events are stored asynchronously, one to a few seconds after the sender gets its reply. A list right after a send can miss the newest requests; use `wait` instead.

## inboxes events get

Get one request with its headers, query, and body.

```bash CLI theme={null}
devhelm inboxes events get <inbox-id> <event-id> -o json
```

* Header names are stored lowercased. Values are stored as sent, including `Authorization`.
* `body` is UTF-8 text clipped at 256 KB; `bodyTruncated` says whether it was cut. Binary bytes decode lossily.
* `sha256` is the hash of the exact body bytes.

## inboxes events raw

Download the exact body bytes. The signed link lasts 60 seconds; the CLI downloads it at once. Compare the file with the event's `sha256`, and verify signatures against these bytes, not against `body`:

```bash CLI theme={null}
devhelm inboxes events raw <inbox-id> <event-id> --file body.bin
sha256sum body.bin
devhelm inboxes events get <inbox-id> <event-id> -o json | jq -r .sha256
```

| Flag | Type | Required | Description |
| - | - | - | - |
| `--file` | string | Yes | File or directory to write. A directory gets `event.bin` |

See [Verify webhook signatures](/testing/webhooks/verify-signatures).

## inboxes events clear

Delete every captured request on the inbox. The inbox and its URL stay.

```bash CLI theme={null}
devhelm inboxes events clear <inbox-id> --yes
```

The CLI cannot delete a single event. Use `event.delete()` in the SDKs, or the API:

```bash API theme={null}
curl -X DELETE https://api.devhelm.io/api/v1/webhook/inboxes/<inbox-id>/events/<event-id> \
  -H "Authorization: Bearer $DEVHELM_API_TOKEN" \
  -H "x-phelm-workspace-id: $DEVHELM_WORKSPACE_ID"
```

## Common errors

| Error | Cause | Fix |
| - | - | - |
| 404 `Workspace doesn't exist` | `DEVHELM_WORKSPACE_ID` is unset or wrong | Export the workspace ID |
| 403 `INBOUND_WEBHOOK_INBOX_LIMIT_REACHED` | The organization is at its inbox cap | Delete unused inboxes, or share one inbox per CI job with a path per test |
| `No matching inbound item arrived before timeout`, exit `11` | No match in time, or `--received-after` was later than the request | Capture the time before the trigger and pass it |
| 429 on `wait` | Over 40 waits per minute in the organization | Back off for the `Retry-After` seconds |

Every code and limit is in [Limits, quotas, and errors](/testing/limits).

## Next steps

<CardGroup cols={2}>
  <Card title="Webhook inboxes" icon="inbox" href="/testing/webhooks/overview">
    What an inbox captures and how ingest works.
  </Card>

  <Card title="Wait for a webhook in a test" icon="hourglass" href="/testing/webhooks/assert-in-tests">
    Vitest and pytest patterns built on wait.
  </Card>

  <Card title="Testing in CI" icon="code-branch" href="/guides/testing-in-ci">
    One inbox per job, secrets, cleanup, and the wait budget.
  </Card>

  <Card title="email commands" icon="envelope" href="/cli/commands/email">
    The CLI for testing mailboxes.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.