feat: add Omega Mac native macOS SwiftUI chat app

Native macOS counterpart to the web Omega agent, connecting directly
to the OpenAI API and TPMJS tool registry (1M+ AI-ready tools).

- SwiftUI app targeting macOS 15+ with dark theme
- Full agentic loop: auto-discover tools via BM25, stream OpenAI
  responses, execute tools via remote sandbox, loop up to 10x
- SwiftData persistence for conversations, messages, tool runs
- Keychain storage for API keys and environment variables
- SSE streaming via URLSession.bytes with custom parser
- Actor-based services (OpenAIService, TPMJSRegistryService)
- NavigationSplitView layout with sidebar + chat detail
- MarkdownUI for rendering assistant responses
- Settings: API key, model picker, env vars, custom system prompt
- Keyboard shortcuts: Cmd+N new chat, Cmd+, settings, Enter send

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Davis 2026-02-09 21:29:39 +10:00
parent c8e8a7d9b4
commit 1cd44b4e97
45 changed files with 4583 additions and 0 deletions

View file

@ -0,0 +1,77 @@
# @tpmjs/tools-agentmail
AgentMail API tools for AI agents — create inboxes, send/receive emails, manage threads, drafts, and more.
## Installation
```bash
npm install @tpmjs/tools-agentmail
```
## Setup
Set the `AGENTMAIL_API_KEY` environment variable. Get your API key from [AgentMail Dashboard](https://app.agentmail.to).
```bash
export AGENTMAIL_API_KEY=your_api_key_here
```
## Usage
```typescript
import { createInbox, sendMessage, listMessages } from '@tpmjs/tools-agentmail';
// Create a new inbox for your AI agent
const inbox = await createInbox.execute({
username: 'my-agent',
display_name: 'My AI Agent',
});
// Send an email
const message = await sendMessage.execute({
inbox_id: inbox.inbox_id,
to: 'recipient@example.com',
subject: 'Hello from AI',
text: 'This is an automated message from my AI agent.',
});
// List received messages
const messages = await listMessages.execute({
inbox_id: inbox.inbox_id,
limit: 20,
});
```
## Tools
| Tool | Description |
|------|-------------|
| createInbox | Create a new email inbox with optional custom username and domain |
| listInboxes | List all email inboxes with pagination support |
| getInbox | Get details of a specific inbox by ID |
| deleteInbox | Delete an inbox and all its messages permanently |
| sendMessage | Send an email message with subject and body |
| replyToMessage | Reply to an existing email in a thread |
| listMessages | List email messages in an inbox with pagination |
| getMessage | Get full details of a specific message |
| listThreads | List email threads, optionally filtered by labels |
| getThread | Get a thread with all its messages |
| createDraft | Create an email draft for approval before sending |
| sendDraft | Send a previously created draft |
## Features
- **Complete Email Management**: Create inboxes, send/receive emails, manage threads
- **Thread Support**: View full conversation history with thread IDs
- **Draft Support**: Create drafts for human-in-the-loop approval workflows
- **Label Support**: Organize messages with custom labels
- **Pagination**: Efficiently handle large inboxes with cursor-based pagination
- **Type-Safe**: Full TypeScript types for all API responses
## API Documentation
For detailed API documentation, visit [AgentMail API Docs](https://docs.agentmail.to).
## License
MIT

View file

@ -0,0 +1,109 @@
{
"name": "@tpmjs/tools-agentmail",
"version": "0.1.0",
"description": "AgentMail API tools for AI agents — create inboxes, send/receive emails, manage threads, drafts, and more",
"type": "module",
"keywords": [
"tpmjs",
"ops",
"ai",
"email",
"agentmail"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.5.1",
"typescript": "^5.9.3"
},
"dependencies": {
"ai": "6.0.49"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/tpmjs/tpmjs.git",
"directory": "packages/tools/official/agentmail"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "ops",
"frameworks": [
"vercel-ai"
],
"env": [
{
"name": "AGENTMAIL_API_KEY",
"description": "AgentMail API key from https://app.agentmail.to",
"required": true
}
],
"tools": [
{
"name": "createInbox",
"description": "Create a new email inbox for an AI agent with optional custom username and domain."
},
{
"name": "listInboxes",
"description": "List all email inboxes in the AgentMail account with pagination support."
},
{
"name": "getInbox",
"description": "Get details of a specific email inbox by its inbox ID."
},
{
"name": "deleteInbox",
"description": "Delete an email inbox and all its messages permanently."
},
{
"name": "sendMessage",
"description": "Send an email message from an inbox to a recipient with subject and body."
},
{
"name": "replyToMessage",
"description": "Reply to an existing email message in a thread conversation."
},
{
"name": "listMessages",
"description": "List email messages in an inbox with pagination support."
},
{
"name": "getMessage",
"description": "Get the full details of a specific email message by its message ID."
},
{
"name": "listThreads",
"description": "List email threads in an inbox, optionally filtered by labels."
},
{
"name": "getThread",
"description": "Get a thread with all its messages for viewing a full conversation."
},
{
"name": "createDraft",
"description": "Create an email draft for human-in-the-loop approval before sending."
},
{
"name": "sendDraft",
"description": "Send a previously created draft, converting it to a sent message."
}
]
}
}

View file

@ -0,0 +1,751 @@
/**
* @tpmjs/tools-agentmail — AgentMail API Tools for AI Agents
*
* Complete email management for AI agents: create inboxes, send/receive emails,
* manage threads, drafts, and more.
*
* @requires AGENTMAIL_API_KEY environment variable
*/
import { jsonSchema, tool } from 'ai';
const BASE_URL = 'https://api.agentmail.to/v0';
// ─── Client Infrastructure ──────────────────────────────────────────────────
function getApiKey(): string {
const key = process.env.AGENTMAIL_API_KEY;
if (!key) {
throw new Error(
'AGENTMAIL_API_KEY environment variable is required. Get your token from https://app.agentmail.to'
);
}
return key;
}
async function apiRequest<T>(
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
path: string,
body?: unknown
): Promise<T> {
const token = getApiKey();
const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
const options: RequestInit = {
method,
headers,
};
if (body !== undefined) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${BASE_URL}${path}`, options);
// Handle 204 No Content for DELETE operations
if (response.status === 204) {
return { success: true } as T;
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`AgentMail HTTP error ${response.status}: ${response.statusText}${errorText ? ` - ${errorText}` : ''}`
);
}
return (await response.json()) as T;
}
// ─── Output Types ────────────────────────────────────────────────────────────
export interface InboxResult {
inbox_id: string;
pod_id: string;
display_name?: string;
created_at: string;
updated_at: string;
}
export interface ListInboxesResult {
count: number;
inboxes: InboxResult[];
next_page_token?: string;
}
export interface SuccessResult {
success: boolean;
inbox_id?: string;
}
export interface MessageResult {
message_id: string;
thread_id: string;
inbox_id: string;
from: string;
to: string;
subject: string;
text: string;
html?: string;
labels: string[];
created_at: string;
}
export interface ListMessagesResult {
count: number;
messages: MessageResult[];
next_page_token?: string;
}
export interface ThreadResult {
thread_id: string;
inbox_id: string;
subject: string;
labels: string[];
message_count: number;
created_at: string;
updated_at: string;
}
export interface ListThreadsResult {
count: number;
threads: ThreadResult[];
next_page_token?: string;
}
export interface ThreadDetailResult {
thread_id: string;
inbox_id: string;
subject: string;
labels: string[];
messages: MessageResult[];
created_at: string;
updated_at: string;
}
export interface DraftResult {
draft_id: string;
inbox_id: string;
to: string;
subject: string;
text: string;
html?: string;
created_at: string;
}
// ─── Inboxes ────────────────────────────────────────────────────────────────
export interface CreateInboxInput {
username?: string;
domain?: string;
display_name?: string;
}
export const createInbox = tool({
description:
'Create a new email inbox for an AI agent with optional custom username and domain.',
inputSchema: jsonSchema<CreateInboxInput>({
type: 'object',
properties: {
username: {
type: 'string',
description: 'Optional username for the inbox. If not provided, a random one is generated.',
},
domain: {
type: 'string',
description: 'Optional domain (defaults to agentmail.to).',
},
display_name: {
type: 'string',
description: 'Optional display name for the inbox.',
},
},
additionalProperties: false,
}),
async execute(input: CreateInboxInput): Promise<InboxResult> {
try {
return await apiRequest<InboxResult>('POST', '/inboxes', {
username: input.username,
domain: input.domain,
display_name: input.display_name,
});
} catch (error) {
throw new Error(`Failed to create inbox: ${(error as Error).message}`);
}
},
});
export interface ListInboxesInput {
limit?: number;
page_token?: string;
}
export const listInboxes = tool({
description: 'List all email inboxes in the AgentMail account with pagination support.',
inputSchema: jsonSchema<ListInboxesInput>({
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Number of inboxes to return (1-100, default: 50).',
},
page_token: {
type: 'string',
description: 'Pagination token from previous response.',
},
},
additionalProperties: false,
}),
async execute(input: ListInboxesInput): Promise<ListInboxesResult> {
try {
if (input.limit !== undefined && (input.limit < 1 || input.limit > 100)) {
throw new Error('Limit must be between 1 and 100');
}
const params = new URLSearchParams();
if (input.limit !== undefined) params.append('limit', String(input.limit));
if (input.page_token) params.append('page_token', input.page_token);
const queryString = params.toString();
const path = queryString ? `/inboxes?${queryString}` : '/inboxes';
return await apiRequest<ListInboxesResult>('GET', path);
} catch (error) {
throw new Error(`Failed to list inboxes: ${(error as Error).message}`);
}
},
});
export interface GetInboxInput {
inbox_id: string;
}
export const getInbox = tool({
description: 'Get details of a specific email inbox by its inbox ID.',
inputSchema: jsonSchema<GetInboxInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID to retrieve.',
},
},
required: ['inbox_id'],
additionalProperties: false,
}),
async execute(input: GetInboxInput): Promise<InboxResult> {
try {
if (!input.inbox_id) {
throw new Error('inbox_id is required and must be non-empty');
}
return await apiRequest<InboxResult>('GET', `/inboxes/${input.inbox_id}`);
} catch (error) {
throw new Error(`Failed to get inbox: ${(error as Error).message}`);
}
},
});
export interface DeleteInboxInput {
inbox_id: string;
}
export const deleteInbox = tool({
description: 'Delete an email inbox and all its messages permanently.',
inputSchema: jsonSchema<DeleteInboxInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID to delete.',
},
},
required: ['inbox_id'],
additionalProperties: false,
}),
async execute(input: DeleteInboxInput): Promise<SuccessResult> {
try {
if (!input.inbox_id) {
throw new Error('inbox_id is required and must be non-empty');
}
await apiRequest<SuccessResult>('DELETE', `/inboxes/${input.inbox_id}`);
return { success: true, inbox_id: input.inbox_id };
} catch (error) {
throw new Error(`Failed to delete inbox: ${(error as Error).message}`);
}
},
});
// ─── Messages ───────────────────────────────────────────────────────────────
export interface SendMessageInput {
inbox_id: string;
to: string;
subject: string;
text: string;
html?: string;
cc?: string;
bcc?: string;
labels?: string[];
}
export const sendMessage = tool({
description: 'Send an email message from an inbox to a recipient with subject and body.',
inputSchema: jsonSchema<SendMessageInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID to send from.',
},
to: {
type: 'string',
description: 'Recipient email address.',
},
subject: {
type: 'string',
description: 'Email subject line.',
},
text: {
type: 'string',
description: 'Plain text email body.',
},
html: {
type: 'string',
description: 'Optional HTML email body.',
},
cc: {
type: 'string',
description: 'Optional CC recipients (comma-separated).',
},
bcc: {
type: 'string',
description: 'Optional BCC recipients (comma-separated).',
},
labels: {
type: 'array',
items: { type: 'string' },
description: 'Optional labels to apply to the message.',
},
},
required: ['inbox_id', 'to', 'subject', 'text'],
additionalProperties: false,
}),
async execute(input: SendMessageInput): Promise<MessageResult> {
try {
if (!input.inbox_id || !input.to || !input.subject || !input.text) {
throw new Error('inbox_id, to, subject, and text are required and must be non-empty');
}
return await apiRequest<MessageResult>('POST', `/inboxes/${input.inbox_id}/messages`, {
to: input.to,
subject: input.subject,
text: input.text,
html: input.html,
cc: input.cc,
bcc: input.bcc,
labels: input.labels,
});
} catch (error) {
throw new Error(`Failed to send message: ${(error as Error).message}`);
}
},
});
export interface ReplyToMessageInput {
inbox_id: string;
message_id: string;
text: string;
html?: string;
labels?: string[];
}
export const replyToMessage = tool({
description: 'Reply to an existing email message in a thread conversation.',
inputSchema: jsonSchema<ReplyToMessageInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID.',
},
message_id: {
type: 'string',
description: 'The message ID to reply to.',
},
text: {
type: 'string',
description: 'Plain text reply body.',
},
html: {
type: 'string',
description: 'Optional HTML reply body.',
},
labels: {
type: 'array',
items: { type: 'string' },
description: 'Optional labels to apply to the reply.',
},
},
required: ['inbox_id', 'message_id', 'text'],
additionalProperties: false,
}),
async execute(input: ReplyToMessageInput): Promise<MessageResult> {
try {
if (!input.inbox_id || !input.message_id || !input.text) {
throw new Error('inbox_id, message_id, and text are required and must be non-empty');
}
return await apiRequest<MessageResult>(
'POST',
`/inboxes/${input.inbox_id}/messages/${input.message_id}/reply`,
{
text: input.text,
html: input.html,
labels: input.labels,
}
);
} catch (error) {
throw new Error(`Failed to reply to message: ${(error as Error).message}`);
}
},
});
export interface ListMessagesInput {
inbox_id: string;
limit?: number;
page_token?: string;
}
export const listMessages = tool({
description: 'List email messages in an inbox with pagination support.',
inputSchema: jsonSchema<ListMessagesInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID to list messages from.',
},
limit: {
type: 'number',
description: 'Number of messages to return (1-100, default: 50).',
},
page_token: {
type: 'string',
description: 'Pagination token from previous response.',
},
},
required: ['inbox_id'],
additionalProperties: false,
}),
async execute(input: ListMessagesInput): Promise<ListMessagesResult> {
try {
if (!input.inbox_id) {
throw new Error('inbox_id is required and must be non-empty');
}
if (input.limit !== undefined && (input.limit < 1 || input.limit > 100)) {
throw new Error('Limit must be between 1 and 100');
}
const params = new URLSearchParams();
if (input.limit !== undefined) params.append('limit', String(input.limit));
if (input.page_token) params.append('page_token', input.page_token);
const queryString = params.toString();
const path = queryString
? `/inboxes/${input.inbox_id}/messages?${queryString}`
: `/inboxes/${input.inbox_id}/messages`;
return await apiRequest<ListMessagesResult>('GET', path);
} catch (error) {
throw new Error(`Failed to list messages: ${(error as Error).message}`);
}
},
});
export interface GetMessageInput {
inbox_id: string;
message_id: string;
}
export const getMessage = tool({
description: 'Get the full details of a specific email message by its message ID.',
inputSchema: jsonSchema<GetMessageInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID.',
},
message_id: {
type: 'string',
description: 'The message ID to retrieve.',
},
},
required: ['inbox_id', 'message_id'],
additionalProperties: false,
}),
async execute(input: GetMessageInput): Promise<MessageResult> {
if (!input.inbox_id) {
throw new Error('inbox_id is required and must be non-empty');
}
if (!input.message_id) {
throw new Error('message_id is required and must be non-empty');
}
try {
return await apiRequest<MessageResult>(
'GET',
`/inboxes/${encodeURIComponent(input.inbox_id)}/messages/${encodeURIComponent(input.message_id)}`
);
} catch (error) {
throw new Error(
`Failed to get message "${input.message_id}" from inbox "${input.inbox_id}": ${(error as Error).message}`
);
}
},
});
// ─── Threads ────────────────────────────────────────────────────────────────
export interface ListThreadsInput {
inbox_id: string;
limit?: number;
page_token?: string;
labels?: string[];
}
export const listThreads = tool({
description: 'List email threads in an inbox, optionally filtered by labels.',
inputSchema: jsonSchema<ListThreadsInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID to list threads from.',
},
limit: {
type: 'number',
description: 'Number of threads to return (1-100, default: 50).',
},
page_token: {
type: 'string',
description: 'Pagination token from previous response.',
},
labels: {
type: 'array',
items: { type: 'string' },
description: 'Optional labels to filter threads by.',
},
},
required: ['inbox_id'],
additionalProperties: false,
}),
async execute(input: ListThreadsInput): Promise<ListThreadsResult> {
if (!input.inbox_id) {
throw new Error('inbox_id is required and must be non-empty');
}
if (input.limit !== undefined && (input.limit < 1 || input.limit > 100)) {
throw new Error('Limit must be between 1 and 100');
}
try {
const params = new URLSearchParams();
if (input.limit !== undefined) params.append('limit', String(input.limit));
if (input.page_token) params.append('page_token', input.page_token);
if (Array.isArray(input.labels)) {
const validLabels = input.labels.filter((label) => typeof label === 'string' && label.trim().length > 0);
for (const label of validLabels) {
params.append('labels', label);
}
}
const queryString = params.toString();
const path = queryString
? `/inboxes/${encodeURIComponent(input.inbox_id)}/threads?${queryString}`
: `/inboxes/${encodeURIComponent(input.inbox_id)}/threads`;
return await apiRequest<ListThreadsResult>('GET', path);
} catch (error) {
throw new Error(
`Failed to list threads for inbox "${input.inbox_id}": ${(error as Error).message}`
);
}
},
});
export interface GetThreadInput {
inbox_id: string;
thread_id: string;
}
export const getThread = tool({
description: 'Get a thread with all its messages for viewing a full conversation.',
inputSchema: jsonSchema<GetThreadInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID.',
},
thread_id: {
type: 'string',
description: 'The thread ID to retrieve.',
},
},
required: ['inbox_id', 'thread_id'],
additionalProperties: false,
}),
async execute(input: GetThreadInput): Promise<ThreadDetailResult> {
try {
if (!input.inbox_id || !input.thread_id) {
throw new Error('inbox_id and thread_id are required and must be non-empty');
}
return await apiRequest<ThreadDetailResult>(
'GET',
`/inboxes/${input.inbox_id}/threads/${input.thread_id}`
);
} catch (error) {
throw new Error(`Failed to get thread: ${(error as Error).message}`);
}
},
});
// ─── Drafts ─────────────────────────────────────────────────────────────────
export interface CreateDraftInput {
inbox_id: string;
to: string;
subject: string;
text: string;
html?: string;
cc?: string;
bcc?: string;
}
export const createDraft = tool({
description: 'Create an email draft for human-in-the-loop approval before sending.',
inputSchema: jsonSchema<CreateDraftInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID to create the draft in.',
},
to: {
type: 'string',
description: 'Recipient email address.',
},
subject: {
type: 'string',
description: 'Email subject line.',
},
text: {
type: 'string',
description: 'Plain text email body.',
},
html: {
type: 'string',
description: 'Optional HTML email body.',
},
cc: {
type: 'string',
description: 'Optional CC recipients (comma-separated).',
},
bcc: {
type: 'string',
description: 'Optional BCC recipients (comma-separated).',
},
},
required: ['inbox_id', 'to', 'subject', 'text'],
additionalProperties: false,
}),
async execute(input: CreateDraftInput): Promise<DraftResult> {
try {
if (!input.inbox_id || !input.to || !input.subject || !input.text) {
throw new Error('inbox_id, to, subject, and text are required and must be non-empty');
}
return await apiRequest<DraftResult>('POST', `/inboxes/${input.inbox_id}/drafts`, {
to: input.to,
subject: input.subject,
text: input.text,
html: input.html,
cc: input.cc,
bcc: input.bcc,
});
} catch (error) {
throw new Error(`Failed to create draft: ${(error as Error).message}`);
}
},
});
export interface SendDraftInput {
inbox_id: string;
draft_id: string;
}
export const sendDraft = tool({
description: 'Send a previously created draft, converting it to a sent message.',
inputSchema: jsonSchema<SendDraftInput>({
type: 'object',
properties: {
inbox_id: {
type: 'string',
description: 'The inbox ID.',
},
draft_id: {
type: 'string',
description: 'The draft ID to send.',
},
},
required: ['inbox_id', 'draft_id'],
additionalProperties: false,
}),
async execute(input: SendDraftInput): Promise<MessageResult> {
if (!input.inbox_id) {
throw new Error('inbox_id is required and must be non-empty');
}
if (!input.draft_id) {
throw new Error('draft_id is required and must be non-empty');
}
try {
const response = await apiRequest<MessageResult>(
'POST',
`/inboxes/${encodeURIComponent(input.inbox_id)}/drafts/${encodeURIComponent(input.draft_id)}/send`
);
if (!response || !response.message_id) {
throw new Error('AgentMail API returned an invalid response when sending draft');
}
return response;
} catch (error) {
throw new Error(
`Failed to send draft "${input.draft_id}" from inbox "${input.inbox_id}": ${(error as Error).message}`
);
}
},
});
// ─── Default Export ─────────────────────────────────────────────────────────
export default {
// Inboxes
createInbox,
listInboxes,
getInbox,
deleteInbox,
// Messages
sendMessage,
replyToMessage,
listMessages,
getMessage,
// Threads
listThreads,
getThread,
// Drafts
createDraft,
sendDraft,
};

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/react-library.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,12 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
sourcemap: true,
target: 'es2022',
treeshake: true,
splitting: false,
});

View file

@ -13105,6 +13105,288 @@ blocks:
description: "Pin confirmation"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
# ---------------------------------------------------------------------------
# AgentMail Tools (email for AI agents)
# ---------------------------------------------------------------------------
ops.agentmailCreateInbox:
type: utility
description: "Create a new email inbox for an AI agent."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API POST /v0/inboxes endpoint"
inputs:
- name: username
type: string
optional: true
description: "Username for the email address"
- name: domain
type: string
optional: true
description: "Domain for the email address"
- name: display_name
type: string
optional: true
description: "Display name for the inbox"
outputs:
- name: inbox
type: InboxResult
description: "Created inbox details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailListInboxes:
type: utility
description: "List all email inboxes in the account."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API GET /v0/inboxes endpoint"
inputs:
- name: limit
type: number
optional: true
description: "Max inboxes to return (1-100)"
- name: page_token
type: string
optional: true
description: "Pagination token"
outputs:
- name: result
type: ListInboxesResult
description: "List of inboxes with pagination"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailGetInbox:
type: utility
description: "Get details of a specific email inbox."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id} endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
outputs:
- name: inbox
type: InboxResult
description: "Inbox details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailDeleteInbox:
type: utility
description: "Delete an email inbox and all its messages."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API DELETE /v0/inboxes/{inbox_id} endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
outputs:
- name: result
type: SuccessResult
description: "Deletion confirmation"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailSendMessage:
type: utility
description: "Send an email message from an inbox."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/messages endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID to send from"
- name: to
type: string
description: "Recipient email address"
- name: subject
type: string
description: "Email subject"
- name: text
type: string
description: "Plain text body"
- name: html
type: string
optional: true
description: "HTML body"
outputs:
- name: message
type: MessageResult
description: "Sent message details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailReplyToMessage:
type: utility
description: "Reply to an existing email message in a thread."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/messages/{message_id}/reply endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: message_id
type: string
description: "Message ID to reply to"
- name: text
type: string
description: "Reply text"
- name: html
type: string
optional: true
description: "Reply HTML body"
outputs:
- name: message
type: MessageResult
description: "Reply message details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailListMessages:
type: utility
description: "List email messages in an inbox."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/messages endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: limit
type: number
optional: true
description: "Max messages to return (1-100)"
- name: page_token
type: string
optional: true
description: "Pagination token"
outputs:
- name: result
type: ListMessagesResult
description: "List of messages with pagination"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailGetMessage:
type: utility
description: "Get the full details of a specific email message."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/messages/{message_id} endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: message_id
type: string
description: "Message ID"
outputs:
- name: message
type: MessageResult
description: "Full message details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailListThreads:
type: utility
description: "List email threads in an inbox, optionally filtered by labels."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/threads endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: limit
type: number
optional: true
description: "Max threads to return (1-100)"
- name: labels
type: array
optional: true
description: "Filter by labels"
outputs:
- name: result
type: ListThreadsResult
description: "List of threads with pagination"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailGetThread:
type: utility
description: "Get a thread with all its messages for viewing a full conversation."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API GET /v0/inboxes/{inbox_id}/threads/{thread_id} endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: thread_id
type: string
description: "Thread ID"
outputs:
- name: thread
type: ThreadDetailResult
description: "Thread with all messages"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailCreateDraft:
type: utility
description: "Create an email draft for human-in-the-loop approval."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/drafts endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: to
type: string
description: "Recipient email"
- name: subject
type: string
description: "Email subject"
- name: text
type: string
description: "Plain text body"
- name: html
type: string
optional: true
description: "HTML body"
outputs:
- name: draft
type: DraftResult
description: "Created draft details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
ops.agentmailSendDraft:
type: utility
description: "Send a previously created draft, converting it to a sent message."
path: "agentmail"
domain_rules:
- id: api_integration
description: "Must call AgentMail API POST /v0/inboxes/{inbox_id}/drafts/{draft_id}/send endpoint"
inputs:
- name: inbox_id
type: string
description: "Inbox ID"
- name: draft_id
type: string
description: "Draft ID"
outputs:
- name: message
type: MessageResult
description: "Sent message details"
measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance]
# =============================================================================
# VALIDATORS - Which validators to run against each block
# =============================================================================