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

# Send, Receive, and Reply to Email from Your AI Agent

> Learn how to send transactional emails, handle inbound messages, reply to threads, and process attachments with your Nonhumans agent inbox.

Every Nonhumans agent gets a real, fully deliverable email address at `{handle}@nonhumans.ai` — no third-party mail service required. SPF, DKIM, and DMARC records are configured automatically, so your agent's emails land in inboxes, not spam folders. Use the Email primitive to send transactional messages, poll for inbound mail, reply to threads, and build complete email-driven workflows — all from the same API key that powers the rest of your agent.

## Your agent's email address

When you reserve a handle, your inbox is provisioned instantly:

| Property    | Value                   |
| ----------- | ----------------------- |
| Address     | `{handle}@nonhumans.ai` |
| SPF         | ✓ Configured            |
| DKIM        | ✓ Signed                |
| DMARC       | ✓ Policy enforced       |
| Attachments | Up to 25 MB per message |

<Note>
  You can also configure a custom domain (e.g. `agent@yourdomain.com`) from the Nonhumans dashboard if you'd prefer your agent to send from your own brand.
</Note>

***

## Sending email

Use `agent.email.send()` to send a message from your agent's address. Both plain-text and HTML bodies are supported — send both for maximum compatibility.

```typescript theme={null}
import { createAgent } from '@nonhumans/sdk';

const agent = createAgent({ apiKey: process.env.NONHUMANS_API_KEY! });

await agent.email.send({
  to: 'customer@example.com',
  subject: 'Your order is ready',
  text: 'Order #123 has shipped.',
  html: '<p>Order <b>#123</b> has shipped.</p>',
});
```

You can also pass an array to `to` for multiple recipients, and include `cc` and `bcc` fields:

```typescript theme={null}
await agent.email.send({
  to: ['alice@example.com', 'bob@example.com'],
  cc: 'manager@example.com',
  subject: 'Weekly digest',
  text: 'Here is this week's summary...',
});
```

***

## Receiving email

Nonhumans supports two patterns for handling inbound mail: **polling** for simple loops, and **webhooks** for real-time, event-driven agents.

### Polling the inbox

Poll your inbox on a schedule using `agent.email.list()`. Filter by folder, date, read status, or sender:

```typescript theme={null}
// Fetch all unread messages received since January 1, 2024
const messages = await agent.email.list({
  folder: 'inbox',
  unread: true,
  since: '2024-01-01',
});

for (const message of messages) {
  console.log(`From: ${message.from} | Subject: ${message.subject}`);
}
```

<Tip>
  Store the timestamp of your last poll in agent memory (`agent.memory.set`) and use it as the `since` value on each cycle — this avoids reprocessing messages you've already handled.
</Tip>

### Webhook (real-time)

For event-driven agents that need to react immediately to new mail, register a webhook endpoint. Nonhumans will POST a JSON payload to your endpoint every time a new message arrives.

```typescript theme={null}
// Register a public webhook endpoint for your agent
const endpoint = await agent.web.endpoint.register({
  path: '/inbound-email',
  events: ['email.received'],
});

console.log(`Webhook URL: ${endpoint.url}`);
// e.g. https://my-agent.nonhumans.ai/inbound-email
```

Your webhook handler receives a payload like:

```typescript theme={null}
// Incoming webhook payload shape
interface InboundEmailEvent {
  event: 'email.received';
  message: {
    id: string;
    from: string;
    to: string[];
    subject: string;
    text: string;
    html: string;
    attachments: Attachment[];
    receivedAt: string; // ISO 8601
  };
}
```

***

## Reading a specific message

Fetch a single message by ID to access its full content, headers, and attachments:

```typescript theme={null}
const message = await agent.email.get('msg_abc123');

console.log(message.subject);
console.log(message.text);
console.log(message.attachments.length); // number of attachments
```

***

## Replying to a message

Reply to any message while preserving the thread context. Nonhumans automatically sets the correct `In-Reply-To` and `References` headers so replies appear as part of the same conversation:

```typescript theme={null}
await agent.email.reply('msg_abc123', {
  text: 'Thanks for reaching out! I'll look into this and follow up shortly.',
  html: '<p>Thanks for reaching out! I'll look into this and follow up shortly.</p>',
});
```

***

## Handling attachments

Attachments are available on any message object. Download attachment content as a `Buffer` using the attachment ID:

```typescript theme={null}
const message = await agent.email.get('msg_abc123');

for (const attachment of message.attachments) {
  console.log(`${attachment.filename} — ${attachment.contentType} (${attachment.size} bytes)`);

  // Download the raw file content
  const buffer = await agent.email.attachment.download(attachment.id);

  // Example: store in agent memory or forward to another service
}
```

You can also send outbound emails with attachments:

```typescript theme={null}
import { readFileSync } from 'fs';

await agent.email.send({
  to: 'client@example.com',
  subject: 'Your invoice',
  text: 'Please find your invoice attached.',
  attachments: [
    {
      filename: 'invoice-jan-2025.pdf',
      content: readFileSync('./invoice.pdf'),
      contentType: 'application/pdf',
    },
  ],
});
```

***

## Example: email triage agent

This agent reads the inbox, uses an LLM to categorize each message, replies to routine questions, and escalates urgent requests to a human.

```typescript theme={null}
import { createAgent } from '@nonhumans/sdk';

const agent = createAgent({ apiKey: process.env.NONHUMANS_API_KEY! });

const ESCALATION_EMAIL = 'human@yourcompany.com';

async function triage() {
  const messages = await agent.email.list({ folder: 'inbox', unread: true });

  for (const message of messages) {
    // Ask the LLM to classify the message
    const classification = await agent.models.chat({
      model: 'gpt-4o',
      messages: [
        {
          role: 'system',
          content: `Classify this email into one of: ROUTINE, URGENT, SPAM.
Return a JSON object: { "category": "ROUTINE" | "URGENT" | "SPAM", "summary": "one sentence" }`,
        },
        {
          role: 'user',
          content: `Subject: ${message.subject}\n\n${message.text}`,
        },
      ],
      responseFormat: 'json',
    });

    const { category, summary } = JSON.parse(classification.content[0].text);

    if (category === 'SPAM') {
      await agent.email.move(message.id, 'spam');
      console.log(`Marked as spam: "${message.subject}"`);
      continue;
    }

    if (category === 'URGENT') {
      // Forward to a human for review
      await agent.email.send({
        to: ESCALATION_EMAIL,
        subject: `[URGENT] ${message.subject}`,
        text: `Escalated by agent.\n\nSummary: ${summary}\n\nOriginal message:\n${message.text}`,
      });
      await agent.email.reply(message.id, {
        text: 'Thank you for your message. A team member will follow up with you shortly.',
      });
      console.log(`Escalated: "${message.subject}"`);
      continue;
    }

    // Handle ROUTINE messages with an LLM-generated reply
    const reply = await agent.models.chat({
      model: 'gpt-4o',
      messages: [
        { role: 'system', content: 'Reply helpfully and concisely to the email below.' },
        { role: 'user', content: `Subject: ${message.subject}\n\n${message.text}` },
      ],
    });

    await agent.email.reply(message.id, {
      text: reply.content[0].text,
    });

    console.log(`Replied to routine message: "${message.subject}"`);
  }
}

triage();
setInterval(triage, 30_000);
```

***

## Rate limits and best practices

<Info>
  Default rate limits are 500 outbound emails per day and 100 per hour. Contact support to increase limits for high-volume agents.
</Info>

| Limit                   | Default |
| ----------------------- | ------- |
| Outbound per hour       | 100     |
| Outbound per day        | 500     |
| Max attachment size     | 25 MB   |
| Max recipients per send | 50      |
| Inbox storage           | 10 GB   |

**Best practices:**

* **Always set `since`** when polling — avoid re-processing old messages by tracking your last-poll timestamp in `agent.memory`.
* **Use webhooks for latency-sensitive workflows** — polling introduces up to 60 seconds of delay depending on your interval.
* **Send both `text` and `html`** — some email clients display only one format; providing both ensures the best rendering everywhere.
* **Mark messages as read** after processing with `agent.email.markRead(message.id)` to keep your inbox state clean.
* **Respect unsubscribe signals** — check for `List-Unsubscribe` headers on inbound messages before adding senders to outbound lists.
