Use email when your app needs managed sending, inbound addresses on the @agentuity.email domain, and durable history of every send and receipt. Start with EmailClient; Hono apps can use c.var.email after installing the Agentuity middleware.
npm install @agentuity/emailimport { EmailClient } from '@agentuity/email';
const email = new EmailClient();
export async function sendWelcomeEmail(from: string, to: string): Promise<string> {
const result = await email.send({
from,
to: [to],
subject: 'Welcome',
text: 'Your workspace is ready.',
html: '<p>Your workspace is ready.</p>',
});
return result.id;
}EmailClient reads AGENTUITY_SDK_KEY, then AGENTUITY_CLI_KEY, from the environment. Keep that key in .env for local development and configure the same variable for deployed apps.
The from address must belong to your organization. Create one with createAddress() first, then store the returned email address in your app config.
When to use email
| Need | Use |
|---|---|
| send transactional or notification email | |
| receive email at managed addresses | |
| receive HTTP callbacks from external services | Webhooks |
| hand email work to a background process | Queues |
Client Setup
Construct the client once at module scope and reuse it from handlers, routes, or scripts.
import { EmailClient } from '@agentuity/email';
const email = new EmailClient({
orgId: process.env.AGENTUITY_CLOUD_ORG_ID,
});| Option | Description |
|---|---|
apiKey | Optional API key. Defaults to AGENTUITY_SDK_KEY, then AGENTUITY_CLI_KEY. |
orgId | Optional organization ID. Used when the API key is org-scoped or when calling from a CLI context. |
url | Optional Email API URL. Defaults to AGENTUITY_EMAIL_URL, then the regional Agentuity email service URL. |
logger | Optional logger instance. |
Create Addresses
Email addresses live under the @agentuity.email domain. Pass the local part you want to own.
const address = await email.createAddress('support');
const addresses = await email.listAddresses();
const current = await email.getAddress(address.id);Address IDs are prefixed with eaddr_. Delete an address only when you no longer need its inbound and outbound history.
await email.deleteAddress(address.id);Send Email
send() queues the outbound email and returns the outbound record. Delivery is asynchronous: initial status is pending, and a later getOutbound() reveals the final outcome.
const outbound = await email.send({
from: address.email,
to: ['customer@example.com'],
subject: 'We received your request',
text: 'Thanks. We will follow up shortly.',
headers: {
'X-Ticket-ID': 'task_123',
},
});
const delivered = await email.getOutbound(outbound.id);
const outboundForAddress = await email.listOutbound(address.id);| Field | Required | Description |
|---|---|---|
from | Yes | Sender address owned by the organization. |
to | Yes | Array of recipient addresses. At least one. |
subject | Yes | Email subject. |
text | No | Plain text body. |
html | No | HTML body. |
attachments | No | Array of base64-encoded attachments. |
headers | No | Custom headers, for example In-Reply-To or References for threading. |
Outbound records can be pending, success, or failed. Failed records carry an error string.
For bursts, publish email work to a queue and let workers call email.send(). That keeps request handlers fast and gives each send a retry boundary.
Attachments
Attachments are sent inline as base64.
const csv = Buffer.from('date,total\n2026-04-28,42').toString('base64');
await email.send({
from: address.email,
to: ['manager@example.com'],
subject: 'Daily report',
text: 'The report is attached.',
attachments: [
{
filename: 'daily-report.csv',
content: csv,
contentType: 'text/csv',
},
],
});The total RFC 822 message, including attachments, must stay within the service size limit for your org (25 MB by default). Encode binary attachments with Buffer.from(bytes).toString('base64') or the equivalent in your runtime.
Inbound Email
listInbound() returns all inbound messages for the org. Pass an address ID to filter to one mailbox.
const allInbound = await email.listInbound();
const supportInbound = await email.listInbound(address.id);
const first = supportInbound.at(0);
const message = first ? await email.getInbound(first.id) : null;Inbound records carry sender, recipient, subject, optional text and HTML, headers, and stored attachment metadata. Each stored attachment has bucket, key, and an optional pre-signed url for download.
await email.deleteInbound('einb_123');Forward Inbound Email to an HTTP Endpoint
A destination forwards each inbound email to an HTTP endpoint as it arrives. This is the usual way to wire inbound mail into your app.
const destination = await email.createDestination(address.id, 'url', {
url: 'https://api.example.com/email/inbound',
method: 'POST',
headers: {
'X-Email-Source': 'agentuity',
},
});
const destinations = await email.listDestinations(address.id);
await email.deleteDestination(address.id, destination.id);createDestination(addressId, type, config) takes three positional arguments. The current type is url. The config.url must be public http or https and must not point at private or loopback addresses. Destination IDs are prefixed with edst_.
Receive Inbound Email from a Server Route
The route below pairs a managed address with a destination so the platform forwards every inbound email to your app. Verify any signature your destination URL expects, then publish, store, or open a task as needed.
import { Hono } from 'hono';
import { agentuity } from '@agentuity/hono';
import type { Services } from '@agentuity/hono';
type Variables = Pick<Services, 'email' | 'queue'>;
const app = new Hono<{ Variables: Variables }>();
app.use('*', agentuity());
app.post('/email/inbound', async (c) => {
const inbound = await c.req.json<{
id: string;
from: string;
to: string;
subject?: string;
text?: string;
}>();
await c.var.queue.publish('email-inbound', inbound, {
idempotencyKey: inbound.id,
});
return c.json({ received: true });
});
export default app;Connection Config
Some workflows need IMAP or POP3 access to an Agentuity address, for example to plug it into an existing mail client.
const config = await email.getConnectionConfig(address.id);
if (config) {
const imap = config.imap;
const pop3 = config.pop3;
}The returned config includes host, port, tls, username, and password for both IMAP and POP3.
The IMAP and POP3 passwords returned by getConnectionConfig() are real credentials. Store them with your other secrets, do not log them, and do not embed them in client-side code.
Activity Counts
getActivity() returns daily inbound and outbound counts.
const activity = await email.getActivity({ days: 30 });days is optional and accepts values between 7 and 365. Values outside that range are clamped. Defaults to 7 when omitted.
Hono Middleware Variant
@agentuity/hono builds EmailClient once and exposes it on c.var.email.
npm install @agentuity/hono honoimport { Hono } from 'hono';
import { agentuity } from '@agentuity/hono';
import type { Services } from '@agentuity/hono';
type Variables = Pick<Services, 'email'>;
const app = new Hono<{ Variables: Variables }>();
app.use('*', agentuity());
app.post('/notify', async (c) => {
const body = await c.req.json<{ to: string }>();
const from = process.env.AGENTUITY_EMAIL_FROM;
if (!from) {
return c.json({ error: 'AGENTUITY_EMAIL_FROM is required' }, 500);
}
const outbound = await c.var.email.send({
from,
to: [body.to],
subject: 'Notification',
text: 'You have a new notification.',
});
return c.json({ id: outbound.id, status: outbound.status }, 202);
});
export default app;Next Steps
- Queues: move email sending or inbound parsing out of the request path
- Webhooks: receive HTTP callbacks alongside inbound email
- Tasks: turn an inbound email into a tracked work item
- Using Standalone Packages: configure service clients outside Agentuity projects
- Email API Reference: inspect REST fields and lower-level method details