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.
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 |
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.
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.
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:
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:
// 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}`);
}
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.
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.
// 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:
// 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:
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:
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:
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:
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.
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
Default rate limits are 500 outbound emails per day and 100 per hour. Contact support to increase limits for high-volume agents.
| 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.