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

# Agent Onboarding

> Resources for AI coding assistants, MCP servers, skills, and agent-friendly documentation.

> Everything you need to onboard your AI agent to PostNuvia, the first email provider built for AI agents.

If you're developing with AI, PostNuvia offers several resources to improve your experience.

## Get an API key

Your agent can sign up programmatically using the Agent API. No console access needed.

**`Python`**

```python title="Python"
from postnuvia import PostNuvia

client = PostNuvia()
response = client.agent.sign_up(human_email="you@example.com", username="my-agent")
# response.api_key  -> store this securely
# response.inbox_id -> my-agent@postnuvia.com
```

**`TypeScript`**

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

const client = new PostNuviaClient();
const response = await client.agent.signUp({ humanEmail: "you@example.com", username: "my-agent" });
// response.apiKey  -> store this securely
// response.inboxId -> my-agent@postnuvia.com
```

**`CLI`**

```bash title="CLI"
postnuvia agent sign-up --human-email you@example.com --username my-agent
```

A 6-digit OTP is sent to the provided email. Verify to unlock full permissions:

**`Python`**

```python title="Python"
client = PostNuvia(api_key=response.api_key)
client.agent.verify(otp_code="123456")
```

**`TypeScript`**

```typescript title="TypeScript"
const authedClient = new PostNuviaClient({ apiKey: response.apiKey });
await authedClient.agent.verify({ otpCode: "123456" });
```

**`CLI`**

```bash title="CLI"
postnuvia agent verify --otp-code 123456
```

> **Info**
>
> The sign-up endpoint is idempotent. Calling it again with the same email rotates the API key and resends the OTP if expired.

> **Warning**
>
> **Unverified accounts can only send email to the attached human.** Until you complete OTP verification, sends to any other address are rejected with a `403` carrying `code: "message_rejected"`, whose `fix` explains that sending is restricted until verification and points at `POST /v0/agent/verify`. An account with no human attached cannot send at all until one is attached with `POST /v0/agent/human`. Always call `agent.verify()` before sending to external recipients. See the [Error Reference](/errors#message_rejected).

> **Note**
>
> Unverified accounts are also denied some permissions, such as creating API keys, managing list entries, and creating, updating, or deleting pods. These requests return a `403` with `code: "missing_permission"` until the organization is verified. See [Agent verification](/permissions#agent-verification) for the full list.

> **Warning**
>
> **Some domains cannot be used for agent signup.** Common placeholder domains (e.g., `example.com`) and certain provider-specific domains are blocklisted. If signup fails, use a real email address from your own domain or a standard email provider.

Alternatively, a human can create an account at [console.postnuvia.com](https://console.postnuvia.com) and generate an API key from the dashboard.

> **Info**
>
> PostNuvia's free tier includes 3 inboxes and 3,000 emails/month, no credit card required. Your agent can start building immediately.

### Sign up without a human email

If your agent doesn't have a human's email yet, leave `human_email` out. The agent still gets an API key and an inbox, but the inbox is **receive-only**: it can receive email and cannot send to anyone.

**`Python`**

```python title="Python"
client = PostNuvia()
response = client.agent.sign_up(username="my-agent")
# response.api_key  -> store this durably, it cannot be recovered
```

**`TypeScript`**

```typescript title="TypeScript"
const client = new PostNuviaClient();
const response = await client.agent.signUp({ username: "my-agent" });
// response.apiKey  -> store this durably, it cannot be recovered
```

**`CLI`**

```bash title="CLI"
postnuvia agent sign-up --username my-agent
```

> **Warning**
>
> **Without a human email, a lost API key cannot be recovered.** Calling sign-up again without `human_email` creates a new organization and inbox, which needs a different `username`: the original username stays with the lost organization. Store the key durably, and attach a human as soon as you can.

Then connect a human, in either of two ways.

**The human claims the inbox in the Console.** This is available for inboxes in the US region, whose keys start with `am_us_`. The agent gives its API key to the human it works for, over a channel they already use (never by email: anyone can email the agent's inbox). The human opens [console.postnuvia.com/claim](https://console.postnuvia.com/claim), signs in or signs up, and pastes the key. The agent's organization becomes one the human owns, on the Free plan, sending is unlocked (at first to at most 3 distinct recipients in the first hour, 5 in the first day, and 10 in the first week), and the agent's key keeps working, though it can take up to 5 minutes to pick up the claim. See [How do I claim my agent's inbox?](/knowledge-base/claiming-agent-inbox) for the human's steps and the errors they can see.

If the claim page refuses the human's account because it cannot create another organization, the human already has a Console account. They create an API key for the agent in their organization instead. That key belongs to their organization, not the one the agent signed up into, so the agent creates a new inbox with it, under a different username: the sign-up inbox stays behind, receiving but unable to send unless a different human claims it or is attached.

**The agent attaches the human's email.** A 6-digit OTP is sent to the human, the agent can then email that human, and verifying with the OTP lifts the remaining restrictions:

**`Python`**

```python title="Python"
client = PostNuvia(api_key=response.api_key)
client.agent.attach_human(human_email="you@example.com")
client.agent.verify(otp_code="123456")
```

**`TypeScript`**

```typescript title="TypeScript"
const authedClient = new PostNuviaClient({ apiKey: response.apiKey });
await authedClient.agent.attachHuman({ humanEmail: "you@example.com" });
await authedClient.agent.verify({ otpCode: "123456" });
```

**`CLI`**

```bash title="CLI"
postnuvia agent attach-human --human-email you@example.com
postnuvia agent verify --otp-code 123456
```

Once a human is attached, only a Console account whose primary email is that address can claim the inbox, and once the agent is verified it can no longer be claimed. An email address that already has a Console account cannot be attached.

Attaching a human is only possible until the organization is verified. Until then:

* Calling it again with the same email does not rotate the API key. It resends the OTP if it was never delivered, or issues a new one if it expired. While the current OTP is still valid, it keeps that OTP and its attempt count, so if all 10 attempts are used up, wait until the OTP expires (24 hours after it was issued) before calling it again. Any unverified agent can use this to get a new OTP, including one that signed up with a `human_email`.
* Calling it with a different email replaces the attached human, for example to fix a typo. An organization can replace its human at most 2 times.
* For up to 5 minutes after attaching, sends to the human can still be rejected with a `429` daily send limit error while the API key's cached limits catch up. Wait and retry: this is not your real limit.

## PostNuvia MCP Server

MCP (Model Context Protocol) is an open protocol that standardizes how applications provide context to LLMs. The PostNuvia MCP server gives your AI agent tools to create inboxes, send emails, manage threads, and more.

### Setup

Add this to your MCP client configuration (Claude Code, Cursor, Codex, etc.). Clients that support remote MCP OAuth should use the bare URL and authenticate in-flow:

```json
{
  "mcpServers": {
    "PostNuvia": {
      "url": "https://mcp.postnuvia.com/mcp"
    }
  }
}
```

For clients that don't implement OAuth but support custom headers, pass the key as an `x-api-key` header instead:

```json
{
  "mcpServers": {
    "PostNuvia": {
      "url": "https://mcp.postnuvia.com/mcp",
      "headers": {
        "x-api-key": "${POSTNUVIA_API_KEY}"
      }
    }
  }
}
```

### Available MCP tools

Your MCP client discovers tools directly from the hosted runtime. See the [current MCP tool catalog](/integrations/mcp#available-tools) for their names, descriptions, and availability.

#### [MCP Server Setup](/integrations/mcp)

Connect to the hosted PostNuvia MCP server. npm and PyPI are compatibility transports for clients that only support stdio.

## PostNuvia Docs for Agents

You can give your agent current docs in three ways:

1. **Full documentation index**

   A structured index of every doc page with descriptions:

   ```
   https://docs.postnuvia.com/llms.txt
   ```
2. **Docs search over MCP**

   An MCP server with a `searchDocs` tool that returns relevant passages with their source URLs:

   ```
   https://docs.postnuvia.com/_mcp/server
   ```
3. **Markdown versions of any page**

   Every doc page is available as Markdown. Append `.md` to any page URL:

   ```
   https://docs.postnuvia.com/quickstart.md
   ```

## PostNuvia Skills

Skills give AI agents specialized knowledge for specific tasks. Install the PostNuvia skill to give your coding assistant full email capabilities:

### Claude Code

```bash
claude-code skills install postnuvia-to/postnuvia-skills/postnuvia
```

### Cursor

```bash
cursor skills install postnuvia-to/postnuvia-skills/postnuvia
```

### Codex

```bash
codex skills install postnuvia-to/postnuvia-skills/postnuvia
```

### Manual Installation

```bash
git clone https://github.com/postnuvia-to/postnuvia-skills.git ~/.skills/postnuvia
```

Then set your API key:

```bash
export POSTNUVIA_API_KEY="your-api-key-here"
```

#### [Skills on GitHub](https://github.com/postnuvia-to/postnuvia-skills)

View the skill source and full documentation.

## Quick start for agents

Sign up, create an inbox, and send your first email:

**`Python`**

```python title="Python"
from postnuvia import PostNuvia

# sign up (no API key needed)
client = PostNuvia()
response = client.agent.sign_up(human_email="you@example.com", username="my-agent")

# after verifying with the OTP sent to your email:
client = PostNuvia(api_key=response.api_key)
client.agent.verify(otp_code="123456")

# create an inbox and send an email
inbox = client.inboxes.create(display_name="My AI Agent")
print(f"Agent email: {inbox.inbox_id}")

client.inboxes.messages.send(
    inbox.inbox_id,
    to="user@example.com",
    subject="Hello from my AI agent",
    text="Hi! I'm an AI agent with my own email address."
)
```

**`TypeScript`**

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

// sign up (no API key needed)
const client = new PostNuviaClient();
const response = await client.agent.signUp({ humanEmail: "you@example.com", username: "my-agent" });

// after verifying with the OTP sent to your email:
const authedClient = new PostNuviaClient({ apiKey: response.apiKey });
await authedClient.agent.verify({ otpCode: "123456" });

// create an inbox and send an email
const inbox = await authedClient.inboxes.create({ displayName: "My AI Agent" });
console.log(`Agent email: ${inbox.inboxId}`);

await authedClient.inboxes.messages.send(inbox.inboxId, {
    to: "user@example.com",
    subject: "Hello from my AI agent",
    text: "Hi! I'm an AI agent with my own email address."
});
```

**`CLI`**

```bash title="CLI"
# sign up and verify
postnuvia agent sign-up --human-email you@example.com --username my-agent
postnuvia agent verify --otp-code 123456

# create an inbox
postnuvia inboxes create --display-name "My AI Agent"

# send an email (replace <inbox_id> with the id from above)
postnuvia inboxes messages send \
  --inbox-id <inbox_id> \
  --to user@example.com \
  --subject "Hello from my AI agent" \
  --text "Hi! I'm an AI agent with my own email address."
```

> **Note**
>
> Already have an API key? Skip the sign-up step and initialize the client directly with your key.

### Receive and reply to emails

**`Python`**

```python title="Python"
# List threads in the inbox
threads = client.inboxes.threads.list(inbox_id=inbox.inbox_id)

# Get the latest thread
thread = client.inboxes.threads.get(
    inbox_id=inbox.inbox_id,
    thread_id=threads.threads[0].thread_id
)

# Reply to the latest message
latest_message = thread.messages[-1]
client.inboxes.messages.reply(
    inbox_id=inbox.inbox_id,
    message_id=latest_message.message_id,
    to=[latest_message.from_],
    text="Thanks for your email! I'll look into this."
)
```

**`TypeScript`**

```typescript title="TypeScript"
// List threads in the inbox
const threads = await authedClient.inboxes.threads.list(inbox.inboxId);

// Get the latest thread
const thread = await authedClient.inboxes.threads.get(
    inbox.inboxId,
    threads.threads[0].threadId
);

// Reply to the latest message
const latestMessage = thread.messages[thread.messages.length - 1];
await authedClient.inboxes.messages.reply(
    inbox.inboxId,
    latestMessage.messageId,
    { to: [latestMessage.from], text: "Thanks for your email! I'll look into this." }
);
```

## AI Builder Integrations

PostNuvia integrates with popular AI development platforms:

#### [Replit](/integrations/replit)

Build email agents on Replit with our template.

#### [LiveKit](/integrate-livekit-agents)

Add email to LiveKit voice agents.

#### [OpenClaw](/integrations/openclaw)

Use PostNuvia with OpenClaw agents.

#### [WebSockets](/websockets)

Real-time email events without webhooks.

## What Makes PostNuvia Different?

Unlike traditional email APIs that are built for one-way transactional email, PostNuvia is built for **two-way agent communication**:

| Feature                | PostNuvia                  | Traditional Email APIs    |
| ---------------------- | -------------------------- | ------------------------- |
| Per-agent inboxes      | ✅ Create thousands via API | ❌ Shared sending domains  |
| Receive & parse emails | ✅ Native with threads      | ⚠️ Limited or add-on      |
| Threaded conversations | ✅ First-class API support  | ❌ Not supported           |
| Allowlists/blocklists  | ✅ Per-inbox controls       | ❌ Not available           |
| Multi-tenant (Pods)    | ✅ Built-in isolation       | ❌ Build it yourself       |
| WebSocket events       | ✅ Real-time streaming      | ❌ Webhooks only           |
| IMAP/SMTP access       | ✅ Full protocol support    | ❌ API-only                |
| Usage-based pricing    | ✅ Pay per email            | ❌ Per-inbox subscriptions |

## Next Steps

#### [Full Quickstart Guide](/quickstart)

Step-by-step setup with environment variables and best practices.

#### [API Reference](/api-reference)

Full interactive API documentation.

#### [Example: Auto-Reply Agent](/examples/auto-reply-agent)

Build an agent that responds to emails in real-time.

#### [FAQ](/resources/faq)

Answers to common questions about email, deliverability, and agent patterns.