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

# Email & SMS

> Inboxes and phone numbers on an identity, outbound send, and the unified inbound message feed.

> [!NOTE]
>
> Email and SMS hang off an [identity](https://vetta.sh/docs/api/identities.md) — the persona is decided by **where** a message arrived, not by who sent it. Deliverability comes from a verified [domain](https://vetta.sh/docs/api/domains.md).

An inbox and a phone number are **identity-level**; the domain behind an inbox is **org-level**. One identity may hold several endpoints, and everything that arrives at any of them lands in the same [message feed](#list-inbound-messages).

## Email inboxes

### The inbox object

**id** `string`

Unique id (e.g. `eml_01H...`).

**object** `string`

Always `email_inbox`.

**identity\_id** `string`

The persona that owns the address.

**domain\_id** `string`

The [domain](https://vetta.sh/docs/api/domains.md) it is hosted on.

**address** `string`

The full address, e.g. `ava-sales@mail.acme.dev`.

**localpart** `string`

The part before the `@`.

**owning\_agent\_id** `string`

The agent mail to this address wakes, as this persona, billed to your credits. Each message that passes the [guards](https://vetta.sh/docs/identity/inbound.md#safety-inbound-is-untrusted) starts one session. The agent must hold the persona. Fixed when the inbox is created.

**wake** `boolean`

Whether mail to this address wakes `owning_agent_id`. `true` for a new inbox. An inbox made before inbound mail woke agents is `false` until you turn it on with [Update an inbox](#update-an-inbox); its mail is still stored.

**status** `string`

`active` or `disabled`.

**created\_at** `string`

Creation timestamp.

### Create an inbox

`POST /v1/identities/{id}/emails` — scope `agents:write`

Give **either** an explicit `address` **or** a `domain_id` — with `domain_id` the localpart is derived from the agent's name (lowercased, hyphenated, capped at 32 characters, then suffixed `-2`, `-3`, … against what is taken).

**owning\_agent\_id** `string` *required*

The agent inbound mail is for (`agt_…`).

**wake** `boolean` (default: true)

Whether mail wakes that agent. `false` stores mail without starting a session.

**address** `string`

A full address to claim. The localpart must match `[a-z0-9._-]{2,64}`. Mutually exclusive with `domain_id`.

**domain\_id** `string`

Host on this domain and auto-derive the localpart. Mutually exclusive with `address`.

```bash cURL theme={"system"}
curl -fsSL https://api.vetta.sh/v1/identities/idn_01H.../emails \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "domain_id": "dom_01H9DM...", "owning_agent_id": "agt_01H..." }'
```

```json Response theme={"system"}
{
  "id": "eml_01H9EM...",
  "object": "email_inbox",
  "identity_id": "idn_01H...",
  "domain_id": "dom_01H9DM...",
  "address": "ava-sales@mail.acme.dev",
  "localpart": "ava-sales",
  "owning_agent_id": "agt_01H...",
  "wake": true,
  "status": "active",
  "created_at": "2026-08-20T17:05:00Z"
}
```

**Errors** — `validation_failed` (400) for a malformed localpart, or for supplying both `address` and `domain_id`; `not_found` (404) for an unknown identity, domain, or agent; `conflict` (409) when the address is already claimed.

### List inboxes

`GET /v1/identities/{id}/emails` — scope `agents:read`. Returns a [paginated list](https://vetta.sh/docs/api/pagination.md) of inbox objects; accepts `limit`.

### Update an inbox

`PATCH /v1/identities/{id}/emails/{eid}` — scope `agents:write`. Turns the wake on or off: whether mail to the address starts a session for its agent. Audited as `email.updated`. Answers the inbox.

**wake** `boolean` *required*

`true` to wake the agent on each message that passes the guards; `false` to store mail only.

### Delete an inbox

`DELETE /v1/identities/{id}/emails/{eid}` — scope `agents:write`. Mail to the address stops being accepted.

### Send email

`POST /v1/identities/{id}/emails/{eid}/send` — scope `sessions:write`

Gated on the domain's `dns_status`. A send attempted before SPF/DKIM are provisioned is refused with a **retryable** `job_not_ready` — verify the [domain](https://vetta.sh/docs/api/domains.md#verify-a-domain) and try again.

**to** `string[]` *required*

Recipient addresses.

**subject** `string` *required*

Subject line.

**text** `string` *required*

Plain-text body.

Answers `202` with a receipt, not a message object — the provider's own message id is deliberately not surfaced. Once the provider accepts it, the message is kept as an [outbound message](#list-sent-messages).

```json Response theme={"system"}
{
  "object": "email_send",
  "accepted": true,
  "from": "ava-sales@mail.acme.dev",
  "to": ["buyer@example.com"],
  "sent_at": "2026-08-20T17:06:00Z"
}
```

**Errors** — `job_not_ready` (409, retryable) while the domain is unverified; `validation_failed` (400); `insufficient_credits` (402).

## Phone numbers

### The phone number object

**id** `string`

Unique id (e.g. `pho_01H...`).

**object** `string`

Always `phone_number`.

**identity\_id** `string`

The persona that owns the number.

**e164** `string`

The number in E.164 form, e.g. `+14155550123`.

**country** `string`

ISO country code.

**capabilities** `object`

`sms_inbound` is live the moment the number is bought. `sms_outbound` is true **iff** `campaign_status` is `approved`. `voice` is always `false` — voice is not supported, and the field exists so its absence is explicit.

**tier** `string`

`sole_prop` or `standard` — the carrier registration tier.

**brand\_status** `string`

`none`, `pending_verification`, `verified`, or `rejected`.

**campaign\_status** `string`

`none`, `pending`, `approved`, or `rejected`. Gates outbound.

**owning\_agent\_id** `string | null`

The agent inbound texts wake, as this persona, billed to your credits. `null` when none is set (bought without one, or its agent was deleted). The agent must hold the persona. Set or clear it with [Update a number](#update-a-number); texts that arrived with no agent set are never woken.

**provisioned\_at** `string | null`

When the carrier released the number.

**created\_at** `string`

Creation timestamp.

### Provision a number

`POST /v1/identities/{id}/phones` — scope `billing:write`

A number is a recurring charge, which is why this needs `billing:write` and not `agents:write`.

**tier** `string` *required*

`sole_prop` or `standard`.

**legal\_name** `string` *required*

The registering entity, as the carrier will see it.

**country** `string`

ISO country code. Defaults to `US`.

**tax\_id** `string`

`standard` tier only. Write-through: handed to the carrier and never stored.

**contact\_phone** `string`

`sole_prop` only — the handset that receives the verification passcode. Never stored.

**e164** `string`

Request a specific number instead of the next available one.

**owning\_agent\_id** `string`

The agent inbound texts wake (`agt_…`). It must hold this persona. Checked before the purchase; if the agent is deleted mid-purchase, the number comes back with `owning_agent_id: null`.

Answers **`202`, not `201`**: the number exists and can receive immediately, but outbound waits on a carrier decision.

**Errors** — `validation_failed` (400) when a tier-specific field is missing or supplied for the wrong tier, or `owning_agent_id` is not an agent id or names an agent that does not hold this persona; `insufficient_credits` (402); `insufficient_scope` (403) without `billing:write`; `not_found` (404) when `owning_agent_id` names no agent in this organization — nothing is bought.

### List numbers

`GET /v1/identities/{id}/phones` — scope `agents:read`. Returns a [paginated list](https://vetta.sh/docs/api/pagination.md); accepts `limit`.

### Update a number

`PATCH /v1/identities/{id}/phones/{pid}` — scope `agents:write`. Returns the number. Recorded in the [audit log](https://vetta.sh/docs/api/audit-logs.md) as `phone.updated`.

**owning\_agent\_id** `string | null` *required*

The agent inbound texts to this number are for (`agt_…`), or `null` to clear it.

The body is strict: any other field is `400` `validation_failed`.

**Errors** — `validation_failed` (400) for another field, or an `owning_agent_id` that is not an agent id or names an agent that does not hold this persona; `not_found` (404) when this identity holds no such number, or `owning_agent_id` names no agent in this organization.

### Release a number

`DELETE /v1/identities/{id}/phones/{pid}` — scope `billing:write`. Stops the recurring charge. The number returns to the carrier and cannot be reclaimed.

### Send SMS

`POST /v1/identities/{id}/phones/{pid}/send` — scope `sessions:write`

**to** `string` *required*

Recipient in E.164 form.

**text** `string` *required*

Message body.

> [!WARNING]
>
> Refused with `compliance_pending` until `campaign_status` is `approved`. That is a **carrier rule**, not a platform one — no scope or plan bypasses it, and inbound keeps working the whole time.

```json Response theme={"system"}
{
  "object": "sms_send",
  "accepted": true,
  "from": "+14155550123",
  "to": "+14155550188",
  "sent_at": "2026-08-20T17:07:00Z"
}
```

## List inbound messages

`GET /v1/identities/{id}/messages` — scope `agents:write`. It holds strangers' words and addresses, so a hosted app's own read-only key can't reach it.

Everything that arrived for this persona, both channels unless one is named.

**channel** `string`

`email` or `sms`. Omit for both.

**limit** `integer`

Page size. See [Pagination](https://vetta.sh/docs/api/pagination.md).

**id** `string`

Unique id (e.g. `inb_01H...`).

**channel** `string`

`email` or `sms`.

**endpoint\_id** `string`

The `eml_`/`pho_` endpoint it arrived at.

**from** `string`

Sender address or number.

**to** `string`

The endpoint address or number.

**subject** `string | null`

Email subject; `null` for SMS.

**text** `string`

Plain-text body.

**auth** `object`

SPF/DKIM/DMARC results for mail, carrier origin for SMS — kept for the audit of why the message was accepted.

**session\_id** `string | null`

The session this message woke, or `null`: a guard held it (`guard_reason`), the endpoint has no owning agent, or the agent couldn't start (it doesn't hold the persona, or the balance is too low). A wake that couldn't start is retried for 15 minutes, so this can still fill in.

**guard\_reason** `string | null`

The [wake guard](https://vetta.sh/docs/identity/inbound.md#safety-inbound-is-untrusted) that held the message, or `null`. A held message is stored and never wakes an agent.

| Value | Held because |
| - | - |
| `sender_rate_limit` | more than 5 an hour from one sender to this inbox or number |
| `endpoint_rate_limit` | more than 20 an hour to this inbox or number, all senders together |
| `thread_turn_cap` | an 11th turn from one sender in one mail thread within a day |
| `auto_reply` | the mail is marked automatic |
| `own_persona` | it came from one of your own personas |

**received\_at** `string`

Arrival timestamp.

**read\_at** `string | null`

When it was marked read; `null` while unread.

Inbound arrives at the platform's own receivers — see [Inbound receivers](https://vetta.sh/docs/api/webhooks.md#inbound-receivers) for how a message becomes a message object.

## Mark a message read

`POST /v1/identities/{id}/messages/{mid}/read` — scope `agents:write`

**read** `boolean` *required*

`true` to mark it read, `false` to put it back to unread.

Returns the message. Marking a read message read again keeps the time it was first read.

```bash cURL theme={"system"}
curl -fsSL https://api.vetta.sh/v1/identities/idn_01H.../messages/inb_01H.../read \
  -H "authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "read": true }'
```

**Errors** — `validation_failed` (400) when `read` is missing or not a boolean; `not_found` (404) when this identity holds no such message.

## List sent messages

`GET /v1/identities/{id}/sent` — scope `agents:write`

Everything the persona sent, newest first: through [Send email](#send-email) and through its agent's own `email.send` tool alike. Returns a [paginated list](https://vetta.sh/docs/api/pagination.md); accepts `limit`.

**id** `string`

Unique id (e.g. `out_01H...`).

**object** `string`

Always `outbound_message`.

**channel** `string`

`email`. Texts are not kept yet.

**endpoint\_id** `string`

The `eml_` inbox it was sent from.

**from** `string`

The sending address.

**to** `string[]`

Recipient addresses.

**subject** `string | null`

Subject line.

**text** `string`

Plain-text body.

**sent\_by\_agent\_id** `string | null`

The agent that sent it with its tool; `null` for a send through the API.

**actor** `object | null`

The key or person that sent it through the API; `null` when an agent sent it.

**sent\_at** `string`

When the provider accepted it.

> [card] [Identities](https://vetta.sh/docs/api/identities.md)
>
> The persona these endpoints hang off.


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