> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://docs.postnuvia.com/llms.txt.

# Inboxes

> Learn how PostNuvia Inboxes act as scalable, API-first email accounts for your agents.

## What is an Inbox?

People are used to the traditional Gmail limitations -- only having one inbox. That's of the past.

An `Inbox` is now a fully loaded, programmatically accessible API resource re-designed for the scale of AI Agents.

Think of it as being similar to a Gmail or Outlook account, but built API-first. Each `Inbox` has a unique email address and serves as the primary resource your agent uses to send and receive emails, giving it a first-class identity on the internet.

Unlike traditional email providers that are designed for human scale, PostNuvia `Inboxes` are built to scale horizontally. You can create tens, hundreds, or even thousands of `Inboxes` for your agents on demand.

> **Tip**
>
> Psst! Rather than sending 1000 emails from 1 `Inbox`, sending 10 emails
> across 100 `Inboxes` actually improves deliverability! Read more about
> optimizing for deliverability [here](/best-practices/email-deliverability)

### The PostNuvia Hierarchy

As the diagram below illustrates, your `organization` is the top-level container that holds all your resources. You can provision many `Inboxes` within your `organization`, each with its own `Threads`, `Messages`, and `Attachments`, allowing you to manage a large fleet of agents seamlessly.

![PostNuvia Organizational Hierarchy](/_fern-img/800085ec8545f404e194c8c6e463be1677715204ac5705be777fa95ace0e6d26.webp)

#### Organization

Your `organization` is the highest-level entity. It acts as a container for
all your `Inboxes`, `Domains`, and API keys, allowing you to manage
everything in one place.

#### Inbox

An `Inbox` is a single, scalable "email account" for your agent. You can
create thousands of `Inboxes` within your organization, each with its own
unique email address.

#### Thread

A `Thread` represents a single conversation. It groups together all replies
and forwards related to an initial email, keeping your interactions
organized.

#### Message

A `Message` is an individual email. It contains the content, sender,
recipients, and any associated metadata or `Attachments`. You can cc humans
at any point in time to keep a "human-in-the-loop"

#### Attachment

An `Attachment` is a file that is sent along with a `Message`. You can
programmatically access and download attachments from incoming `Messages`.

## Core Capabilities

Here at PostNuvia we've now made an `Inbox` an API resource, meaning you can perform standard CRUD operations on it. Here are the core capabilities you'll use to manage your `Inboxes`.

```python
from postnuvia import PostNuvia

# Initialize the client
client = PostNuvia(api_key="YOUR_API_KEY")

# --- Create an Inbox ---
# Creates a new inbox with a default postnuvia.com domain
new_inbox = client.inboxes.create()
print(f"Created Inbox: {new_inbox.inbox_id}")

# --- Retrieve an Inbox ---
# Gets a specific inbox by its ID
retrieved_inbox = client.inboxes.get(inbox_id = 'my_name@domain.com')
print(f"Retrieved Inbox: {retrieved_inbox.inbox_id}")

# --- List Inboxes ---
# Lists all inboxes in your organization
all_inboxes = client.inboxes.list()

print(f"Total Inboxes: {all_inboxes.count}")

```

**`TypeScript`**

```typescript title="TypeScript"
import { PostNuviaClient } from "postnuvia";

// Initialize the client
const client = new PostNuviaClient({ apiKey: "YOUR_API_KEY" });

// --- Create an Inbox ---
// Creates a new inbox with a default postnuvia.com domain
const newInbox = await client.inboxes.create({
  username: "docs-testing",
  domain: "domain.com",
  displayName: "Docs Tester",
});
console.log(`Created Inbox: ${newInbox.id}`);

// --- Retrieve an Inbox ---
// Gets a specific inbox by its ID
const inboxId = newInbox.id;
const retrievedInbox = await client.inboxes.get(inboxId);
console.log(`Retrieved Inbox: ${retrievedInbox.inbox_id}`);

// --- List Inboxes ---
// Lists all inboxes in your organization
const allInboxes = await client.inboxes.list();
console.log(`Total Inboxes: ${allInboxes.count}`);



```

**`CLI`**

```bash title="CLI"
# create an inbox
postnuvia inboxes create \
  --username docs-testing \
  --domain domain.com \
  --display-name "Docs Tester"

# get an inbox
postnuvia inboxes get --inbox-id my_name@domain.com

# list all inboxes
postnuvia inboxes list
```

> **Tip**
>
> When creating an `Inbox`, the `username` and `domain` are optional. If you
> don't provide them, PostNuvia will generate a unique address for you using our
> default domain. You can also set `domain` to one of your verified custom
> domains, or to any subdomain of a domain with [subdomains enabled](/custom-domains#setting-up-subdomains). Check out our [guide on managing domains](/managing-domains).

## Profile pictures

The recipient's email provider controls the picture displayed beside your emails. PostNuvia does not currently expose a profile-picture field; `display_name` changes the sender name only. See [Inbox profile pictures](/knowledge-base/inbox-profile-pictures) for Google account photos, BIMI logos, and their requirements.

## Metadata

Attach your own key-value data to any `Inbox` with the `metadata` field. Use it to link an inbox to records in your own system: a user ID, an agent name, or feature flags. Metadata is returned on every inbox response.

Values may be a string, number, or boolean. An `Inbox` can hold up to 256 keys, and each key and string value is limited to 256 characters.

**`Python`**

```python title="Python"
# Attach metadata when creating an inbox; values may be a string, int, float, or boolean
inbox = client.inboxes.create(
    username="support-agent",
    metadata={
        "tenant_id": "acme",       # string
        "seat_count": 5,           # int
        "monthly_spend": 19.99,    # float
        "active": True,            # boolean
    },
)

# Read it back from any inbox response
print(inbox.metadata)
```

**`TypeScript`**

```typescript title="TypeScript"
// Attach metadata when creating an inbox; values may be a string, int, float, or boolean
const inbox = await client.inboxes.create({
  username: "support-agent",
  metadata: {
    tenant_id: "acme", // string
    seat_count: 5, // int
    monthly_spend: 19.99, // float
    active: true, // boolean
  },
});

// Read it back from any inbox response
console.log(inbox.metadata);
```

### Updating metadata

Updates **merge** into the inbox's existing metadata — keys you include are added or overwritten, and keys you omit are preserved.

> **Note**
>
> To remove a single key, send it with a `null` value. To clear all metadata at
> once, send `metadata` as `null`. Every update must include at least one of
> `display_name`, `status`, or `metadata`.

**`Python`**

```python title="Python"
# Add or overwrite keys; the keys you omit stay unchanged
client.inboxes.update(
    inbox_id="support-agent@postnuvia.com",
    metadata={"tier": "enterprise"},
)

# Remove a single key
client.inboxes.update(
    inbox_id="support-agent@postnuvia.com",
    metadata={"tier": None},
)

# Clear all metadata
client.inboxes.update(
    inbox_id="support-agent@postnuvia.com",
    metadata=None,
)
```

**`TypeScript`**

```typescript title="TypeScript"
// Add or overwrite keys; the keys you omit stay unchanged
await client.inboxes.update("support-agent@postnuvia.com", {
  metadata: { tier: "enterprise" },
});

// Remove a single key
await client.inboxes.update("support-agent@postnuvia.com", {
  metadata: { tier: null },
});

// Clear all metadata
await client.inboxes.update("support-agent@postnuvia.com", {
  metadata: null,
});
```

## Pausing an inbox

Pause an inbox to stop it sending and receiving mail without deleting it. The address stays yours, and you can resume the inbox at any time. Pausing and resuming need the `inbox_update` permission.

> **Warning**
>
> Mail sent to a paused inbox is not delivered. It is not bounced, the sender
> is not told, and it is not delivered when you resume the inbox.

While an inbox is paused:

* **Sending is blocked.** Send, reply, forward, and sending a draft return `403` with code [`inbox_paused`](/errors#inbox_paused). A retry of a send that already completed before the pause, with the same `Idempotency-Key`, still returns the original result; it sends nothing.
* **Scheduled drafts fail.** A draft scheduled to send while the inbox is paused is not sent and its `send_status` becomes `failed`. Resuming the inbox does not retry it.
* **Incoming mail is not delivered**, and no `message.received` event is sent for it. A message also addressed to other inboxes is still delivered to them.
* **AgentID sign-ins are refused** for the inbox.
* **Everything already in the inbox is kept.** Its threads, messages, drafts, and API keys stay in place.

A paused inbox has `status: "paused"`. Treat any other value, or no `status` field, as an inbox that sends and receives normally.

The current SDK releases do not accept `status` yet, so call the API directly:

```bash
# pause the inbox
curl -X PATCH "https://api.postnuvia.com/v0/inboxes/support-agent@postnuvia.com" \
  -H "Authorization: Bearer $POSTNUVIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'

# resume it
curl -X PATCH "https://api.postnuvia.com/v0/inboxes/support-agent@postnuvia.com" \
  -H "Authorization: Bearer $POSTNUVIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "active"}'
```

To check whether an inbox is paused, read `status` from `GET /v0/inboxes/{inbox_id}`. If two requests change the status at once, one returns `409` with code [`race_condition`](/errors#race_condition): read the inbox again before retrying.

You can also create an inbox already paused by passing `status: "paused"` to create.

## Inbox-scoped API keys

You can create API keys that are restricted to a single inbox. An inbox-scoped key can only access that inbox's threads, messages, and drafts. This is useful when you want to give an agent or integration the minimum access it needs.

```python
# Create a key scoped to one inbox
key = client.inboxes.api_keys.create(
    new_inbox.inbox_id,
    name="support-agent-key"
)

# The full key is only returned once
print(key.api_key)
```

**`TypeScript`**

```typescript title="TypeScript"
const key = await client.inboxes.apiKeys.create(newInbox.id, {
  name: "support-agent-key",
});

// The full key is only returned once
console.log(key.apiKey);
```

See the [Multi-Tenancy guide](/multi-tenancy#inbox-scoped-keys) for more on scoped keys.

## Copy for Cursor / Claude

Copy one of the blocks below into Cursor or Claude for complete Inboxes API knowledge in one shot.

**`Python`**

```python title="Python"
"""
PostNuvia Inboxes — copy into Cursor/Claude.

Setup: pip install postnuvia python-dotenv. Set POSTNUVIA_API_KEY in .env.

API reference:
- inboxes.create(username?, domain?, display_name?, client_id?, metadata?) — client_id for idempotent retries; metadata is custom key-value data
- inboxes.get(inbox_id)
- inboxes.list(limit?, page_token?)
- inboxes.update(inbox_id, display_name?, metadata?) — at least one field required; metadata is merged, not replaced
- pause/resume: not in the SDK yet; PATCH /v0/inboxes/{inbox_id} with {"status": "paused"} stops sending and receiving, {"status": "active"} resumes
- inboxes.delete(inbox_id)
- inboxes.api_keys.create(inbox_id, name) — inbox-scoped key
- inboxes.api_keys.list(inbox_id)
- inboxes.api_keys.delete(inbox_id, api_key_id)

Errors: SDK raises on 4xx/5xx. Rate limit: 429 with Retry-After.
"""
import os
from dotenv import load_dotenv
from postnuvia import PostNuvia

load_dotenv()
client = PostNuvia(api_key=os.getenv("POSTNUVIA_API_KEY"))

# Create (client_id for idempotent retries)
inbox = client.inboxes.create(client_id="my-inbox-v1")

# Get, list
retrieved = client.inboxes.get(inbox.inbox_id)
all_inboxes = client.inboxes.list(limit=20)
print(f"Total: {all_inboxes.count}")
```

**`TypeScript`**

```typescript title="TypeScript"
/**
 * PostNuvia Inboxes — copy into Cursor/Claude.
 *
 * Setup: npm install postnuvia dotenv. Set POSTNUVIA_API_KEY in .env.
 *
 * API reference:
 * - inboxes.create({ username?, domain?, displayName?, clientId?, metadata? }) — metadata is custom key-value data
 * - inboxes.get(inboxId)
 * - inboxes.list({ limit?, pageToken? })
 * - inboxes.update(inboxId, { displayName?, metadata? }) — at least one field required
 * - pause/resume: not in the SDK yet; PATCH /v0/inboxes/{inbox_id} with {"status": "paused"} stops sending and receiving, {"status": "active"} resumes
 * - inboxes.delete(inboxId)
 * - inboxes.apiKeys.create(inboxId, { name }) — inbox-scoped key
 * - inboxes.apiKeys.list(inboxId)
 * - inboxes.apiKeys.delete(inboxId, apiKeyId)
 *
 * Errors: SDK throws on 4xx/5xx. Rate limit: 429 with Retry-After.
 */
import { PostNuviaClient } from "postnuvia";
import "dotenv/config";

const client = new PostNuviaClient({ apiKey: process.env.POSTNUVIA_API_KEY! });

async function main() {
  const inbox = await client.inboxes.create({ clientId: "my-inbox-v1" });
  const retrieved = await client.inboxes.get(inbox.inboxId);
  const allInboxes = await client.inboxes.list({ limit: 20 });
  console.log("Total:", allInboxes.count);
}
main();
```