@petter100/emai
v0.2.0
Published
AI-first unified email toolkit for agents — read, send, search, classify, extract, and manage email across any provider
Maintainers
Readme
Read, send, search, classify, extract, and manage email across any provider — with AI built in.
npm install @petter100/emaiWhy emai?
AI agents need email capabilities — reading inboxes, understanding content, extracting data from attachments, composing replies, and managing messages. But today's email landscape is fragmented: Gmail API differs from Microsoft Graph differs from IMAP, and bolting on AI features means stitching together multiple libraries.
emai unifies everything into one toolkit:
| Capability | What it does | |------------|-------------| | Any provider | Gmail, Outlook, IMAP/SMTP through a single API | | AI-native | Classification, semantic search, smart compose, extraction, summarization, priority scoring | | Attachment intelligence | Parse PDFs, images (OCR + vision), Office docs, CSV, video | | Agent-ready | npm package, MCP server (37 tools), and CLI — agents can install and start using immediately | | Safety built in | PII scanning, credential detection, phishing analysis, human-in-the-loop approval | | Real-time | IMAP IDLE, webhooks, event streaming |
Table of Contents
- Quick Start
- Providers
- AI Adapters
- AI Features
- Email Operations
- Threading
- Search
- Attachments
- Safety
- Events & Real-Time
- MCP Server
- CLI
- Playground
- Advanced
- API Reference
- Optional Dependencies
- License
Quick Start
import { Emai } from '@petter100/emai';
const emai = new Emai({
provider: {
type: 'imap',
imap: { host: 'imap.gmail.com', port: 993, secure: true, auth: { user: '[email protected]', pass: 'app-password' } },
smtp: { host: 'smtp.gmail.com', port: 465, secure: true, auth: { user: '[email protected]', pass: 'app-password' } },
},
ai: {
adapter: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4o',
},
search: { store: 'memory' },
});
await emai.connect();
// List recent emails
const { items } = await emai.emails.list({ limit: 10 });
// Classify an email
const classification = await emai.ai.classify(items[0]);
// → { category: 'support', confidence: 0.95, sentiment: 'negative', isUrgent: true }
// Semantic search
await emai.search.index(items);
const results = await emai.search.semantic('invoices from last quarter');
// Extract structured data
import { z } from 'zod';
const InvoiceSchema = z.object({
invoiceNumber: z.string(),
amount: z.number(),
dueDate: z.string(),
vendor: z.string(),
});
const extracted = await emai.ai.extract(items[0], InvoiceSchema);
// → { data: { invoiceNumber: 'INV-2026-001', amount: 1500, ... }, confidence: 0.92 }
// AI-compose a reply
const reply = await emai.ai.reply(items[0], {
instructions: 'Thank them and confirm we received the invoice',
tone: 'professional',
});
// Send
await emai.emails.send({
to: '[email protected]',
subject: reply.subject ?? 'Re: Invoice',
text: reply.text,
});
await emai.disconnect();Providers
Gmail API
const emai = new Emai({
provider: {
type: 'gmail',
credentials: {
clientId: process.env.GMAIL_CLIENT_ID,
clientSecret: process.env.GMAIL_CLIENT_SECRET,
refreshToken: process.env.GMAIL_REFRESH_TOKEN,
},
},
});Requires the googleapis package: npm install googleapis
Microsoft Outlook
const emai = new Emai({
provider: {
type: 'outlook',
credentials: {
clientId: process.env.OUTLOOK_CLIENT_ID,
clientSecret: process.env.OUTLOOK_CLIENT_SECRET,
tenantId: process.env.OUTLOOK_TENANT_ID, // optional, for multi-tenant
refreshToken: process.env.OUTLOOK_REFRESH_TOKEN,
},
},
});Requires: npm install @microsoft/microsoft-graph-client
IMAP/SMTP (any provider)
Works with any IMAP/SMTP-compatible email service — Gmail, Yahoo, Fastmail, self-hosted, etc.
const emai = new Emai({
provider: {
type: 'imap',
imap: {
host: 'imap.example.com',
port: 993,
secure: true,
auth: { user: '[email protected]', pass: 'your-password' },
},
smtp: {
host: 'smtp.example.com',
port: 465,
secure: true,
auth: { user: '[email protected]', pass: 'your-password' },
},
},
});OAuth2 is also supported:
auth: { user: '[email protected]', accessToken: 'oauth2-access-token' }Requires: npm install imapflow nodemailer mailparser
AI Adapters
emai is LLM-agnostic. Pick any adapter — or bring your own:
| Adapter | Package | Default Model | Default Embedding Model |
|---------|---------|---------------|------------------------|
| openai | openai | gpt-4o | text-embedding-3-small |
| anthropic | @anthropic-ai/sdk | claude-sonnet-4-20250514 | (native) |
| google | @google/generative-ai | gemini-2.0-flash | text-embedding-004 |
| ollama | ollama | llama3.1 | nomic-embed-text |
// OpenAI
ai: { adapter: 'openai', apiKey: '...', model: 'gpt-4o' }
// Anthropic
ai: { adapter: 'anthropic', apiKey: '...', model: 'claude-sonnet-4-20250514' }
// Google Gemini
ai: { adapter: 'google', apiKey: '...', model: 'gemini-2.0-flash' }
// Local via Ollama
ai: { adapter: 'ollama', model: 'llama3.1', baseUrl: 'http://localhost:11434' }
// Custom adapter (see Advanced section)
ai: { adapter: myCustomAdapter }Full AI Configuration
ai: {
adapter: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4o', // Text/chat model
embeddingModel: 'text-embedding-3-small', // For semantic search
baseUrl: 'https://your-proxy.com/v1', // Custom endpoint
temperature: 0.3, // 0-1, default 0.3
maxTokens: 4096, // Default 4096
}AI Features
Classification
Categorize emails into 15 categories with sentiment analysis and urgency detection.
const result = await emai.ai.classify(email);Returns:
{
category: 'support', // 15 categories (see below)
confidence: 0.95, // 0-1
sentiment: 'negative', // 'positive' | 'negative' | 'neutral'
isUrgent: true,
isActionRequired: true,
labels: ['customer-issue', 'billing'],
reasoning: 'Customer reports billing discrepancy...'
}Categories: primary, social, promotions, updates, forums, spam, phishing, support, sales, billing, newsletter, notification, personal, work, other
Batch classification:
const results = await emai.ai.classifyBatch(emails);
// → ClassificationResult[] — one per emailSummarization
Generate concise summaries with key points, action items, and sentiment.
const summary = await emai.ai.summarize(email);Returns:
{
summary: 'John requested the Q1 revenue report by Friday...',
keyPoints: ['Q1 report needed', 'Deadline is Friday', 'Include regional breakdown'],
participants: [{ name: 'John', address: '[email protected]' }],
actionItems: [
{ description: 'Send Q1 report', assignee: 'you', dueDate: '2026-03-07', priority: 'high', status: 'pending' }
],
sentiment: 'neutral',
topicTags: ['reporting', 'finance', 'deadline']
}Thread summarization:
const threadSummary = await emai.ai.summarizeThread(thread);
// Summarizes the entire conversation across all messages
const batchSummary = await emai.ai.summarizeBatch(emails);
// Digest-style summary of multiple emailsStructured Data Extraction
Extract typed, validated data from emails using Zod schemas.
import { z } from 'zod';
const OrderSchema = z.object({
orderId: z.string(),
items: z.array(z.object({
name: z.string(),
quantity: z.number(),
price: z.number(),
})),
total: z.number(),
shippingAddress: z.string(),
});
const { data, confidence, sources } = await emai.ai.extract(email, OrderSchema);
// data is fully typed as z.infer<typeof OrderSchema>Returns:
{
data: {
orderId: 'ORD-2026-4521',
items: [{ name: 'Widget Pro', quantity: 2, price: 49.99 }],
total: 99.98,
shippingAddress: '123 Main St, San Francisco, CA 94102'
},
confidence: 0.92,
sources: [
{ field: 'orderId', source: 'body', span: 'Order #ORD-2026-4521' },
{ field: 'total', source: 'body', span: 'Total: $99.98' }
]
}Smart Compose & Reply
AI-powered email composition with tone and length control.
// Compose a new email
const draft = await emai.ai.compose({
context: 'Schedule a meeting to discuss Q1 results',
tone: 'professional',
length: 'short',
language: 'en',
});
// → { subject: 'Q1 Results Discussion', text: '...', html: '...' }
// Reply to an email
const reply = await emai.ai.reply(email, {
instructions: 'Decline politely, suggest next week instead',
tone: 'friendly',
});
// → { subject: 'Re: Meeting Request', text: '...', html: '...' }Tone options: professional, casual, friendly, formal, empathetic
Length options: short, medium, long
Tone rewriting & writing improvement:
const rewritten = await emai.ai.rewriteInTone(text, 'professional');
const improved = await emai.ai.improveWriting(text);Priority Scoring
Score email importance on a 0-100 scale with contextual reasoning.
const priority = await emai.ai.prioritize(email, {
userEmail: '[email protected]',
vipList: ['[email protected]', '[email protected]'],
});Returns:
{
score: 85, // 0-100
level: 'high', // 'critical' | 'high' | 'medium' | 'low' | 'none'
reasoning: 'From VIP sender, contains deadline, requires action',
suggestedResponseTime: '2 hours' // 'immediate' | 'within 1 hour' | 'today' | 'this week' | 'when convenient'
}Batch prioritization:
const results = await emai.ai.prioritizeBatch(emails, { userEmail: '[email protected]' });
// → Array<{ email: Email, priority: PriorityResult }>Action Item Detection
Extract tasks, deadlines, requests, and follow-ups from emails.
const actions = await emai.ai.detectActions(email);Returns:
[
{
description: 'Send Q1 report to finance team',
assignee: 'you',
dueDate: '2026-03-07',
priority: 'high',
status: 'pending'
},
{
description: 'Schedule follow-up meeting',
assignee: '[email protected]',
dueDate: null,
priority: 'medium',
status: 'pending'
}
]Thread-level action detection:
const threadActions = await emai.ai.detectActionsInThread(thread);
// Detects actions across the entire conversation, deduplicating completed itemsEmail Operations
Reading Emails
// List emails with filtering
const { items, total, hasMore, nextCursor } = await emai.emails.list({
folder: 'inbox',
limit: 20,
offset: 0,
isRead: false, // Unread only
isStarred: true, // Starred only
hasAttachment: true, // With attachments
from: '[email protected]',
subject: 'invoice',
after: new Date('2026-01-01'),
before: new Date('2026-03-01'),
sortBy: 'date',
sortOrder: 'desc',
});
// Pagination with cursor
const page2 = await emai.emails.list({ cursor: nextCursor, limit: 20 });
// Get a single email
const email = await emai.emails.get('email-id');Email object shape:
interface Email {
id: string;
threadId?: string;
provider: string;
from: { name?: string; address: string };
to: Array<{ name?: string; address: string }>;
cc: Array<{ name?: string; address: string }>;
bcc: Array<{ name?: string; address: string }>;
subject: string;
body: { text?: string; html?: string; markdown?: string };
attachments: Attachment[];
labels: string[];
folder: string;
date: Date;
receivedDate: Date;
isRead: boolean;
isStarred: boolean;
isDraft: boolean;
snippet?: string;
headers: Record<string, string>;
}Sending Emails
const result = await emai.emails.send({
to: '[email protected]', // string, array, or EmailAddress
cc: ['[email protected]', '[email protected]'],
bcc: '[email protected]',
subject: 'Hello from emai',
text: 'Plain text body',
html: '<h1>HTML body</h1>',
attachments: [
{ filename: 'report.pdf', content: pdfBuffer, contentType: 'application/pdf' }
],
replyTo: '[email protected]',
headers: { 'X-Custom-Header': 'value' },
});
// → { id: '...', threadId: '...', messageId: '<[email protected]>' }Reply and forward:
// Reply
await emai.emails.reply('email-id', {
text: 'Thanks for the update!',
replyAll: true, // Reply to all recipients
});
// Forward
await emai.emails.forward('email-id', {
to: '[email protected]',
text: 'FYI — see below.',
});Drafts
const draft = await emai.emails.createDraft({
to: '[email protected]',
subject: 'Draft email',
text: 'Work in progress...',
});
await emai.emails.updateDraft(draft.id, {
text: 'Updated content',
});
await emai.emails.deleteDraft(draft.id);Managing Emails
await emai.emails.markAsRead('email-id');
await emai.emails.markAsUnread('email-id');
await emai.emails.star('email-id');
await emai.emails.unstar('email-id');
await emai.emails.moveToFolder('email-id', 'archive');
await emai.emails.archive('email-id');
await emai.emails.delete('email-id');Labels & Folders
// Labels
const labels = await emai.labels.list();
await emai.labels.add('email-id', 'important');
await emai.labels.remove('email-id', 'important');
await emai.labels.create('my-label', '#ff0000'); // with optional color
await emai.labels.delete('label-id');
// Folders
const folders = await emai.folders.list();
// → [{ id, name, path, type, unreadCount, totalCount, children }]
await emai.folders.create('Projects', parentId);
await emai.folders.delete('folder-id');Threading
Automatic conversation thread detection using headers, subjects, and participant analysis.
// Get a thread by ID
const thread = await emai.threads.get('thread-id');
// → { id, subject, emails: [...], participants, lastDate, messageCount, labels }
// Detect threads from a list of emails
const threads = emai.threads.detect(emails);
// Groups emails into threads using:
// 1. In-Reply-To / References headers
// 2. Subject normalization (strips Re:/Fw:)
// 3. Participant overlap analysisSearch
Semantic Search
Find emails by meaning, not just keywords. Requires an AI adapter with embedding support.
// First, index your emails
await emai.search.index(emails);
// Then search by meaning
const results = await emai.search.semantic('complaints about shipping delays');
// Finds relevant emails even if they don't contain those exact wordsFull-Text Search
Keyword search with operator support. Works without an AI adapter.
const results = await emai.search.fullText('from:john subject:invoice has:attachment');Operators: from:, to:, subject:, has:attachment, is:unread, is:starred, after:, before:, label:
Hybrid Search
Combine semantic understanding with keyword precision:
const results = await emai.search.hybrid('quarterly revenue report', {
alpha: 0.7, // 1.0 = pure semantic, 0.0 = pure full-text
limit: 10,
minScore: 0.5,
});Search Options
All search methods accept:
{
limit?: number, // Default: 10
offset?: number,
folder?: string, // Filter by folder
label?: string, // Filter by label
from?: string, // Filter by sender
after?: Date, // After date
before?: Date, // Before date
minScore?: number, // Minimum relevance score
}Vector Stores
Choose where to store search vectors:
| Store | Package | Best For |
|-------|---------|----------|
| memory | (built-in) | Development, small mailboxes |
| sqlite | better-sqlite3 | Local persistent storage |
| pgvector | pg | Production with PostgreSQL |
| pinecone | @pinecone-database/pinecone | Managed cloud vector DB |
| weaviate | weaviate-client | Hybrid search at scale |
| chromadb | chromadb | Local/self-hosted vector DB |
// Memory (default, no persistence)
search: { store: 'memory' }
// SQLite (local file)
search: { store: 'sqlite', path: './emails.db' }
// PostgreSQL + pgvector
search: { store: 'pgvector', connectionString: 'postgresql://user:pass@localhost/emails' }
// Pinecone
search: { store: 'pinecone', apiKey: '...', indexName: 'emails', environment: 'us-east-1-aws' }
// Weaviate
search: { store: 'weaviate', url: 'http://localhost:8080', collectionName: 'emails' }
// ChromaDB
search: { store: 'chromadb', url: 'http://localhost:8000', collectionName: 'emails' }Attachments
Parse any attachment format with configurable depth:
// Auto-detect and parse
const parsed = await emai.attachments.parse(attachment, { depth: 'deep' });
// → { text, markdown, metadata, pages, tables, images, structuredData }
// Get plain text from any attachment
const text = await emai.attachments.toText(attachment);
// OCR scanned documents and images
const ocrText = await emai.attachments.ocr(attachment);
// Vision AI description
const description = await emai.attachments.describe(attachment);
// Structured data extraction from attachments
const data = await emai.attachments.extract(attachment, InvoiceSchema);
// Download attachment content
const content = await emai.attachments.getContent('email-id', 'attachment-id');Supported Formats
| Format | Extensions | Required Package |
|--------|-----------|-----------------|
| PDF | .pdf | pdf-parse |
| Images | .jpg, .png, .gif, .webp | sharp (processing), tesseract.js (OCR) |
| Word | .docx | mammoth |
| Excel | .xlsx | (built-in) |
| PowerPoint | .pptx | (built-in) |
| CSV | .csv | papaparse |
| Plain text | .txt, .md, .html | (built-in) |
Parse Depth Levels
| Depth | What It Does |
|-------|-------------|
| basic | Metadata and raw text extraction |
| medium | Full text, tables, markdown conversion |
| deep | OCR, vision AI analysis, image descriptions, table extraction |
Safety
Built-in security scanning for outbound emails:
const emai = new Emai({
// ...provider, ai config...
safety: {
piiScanning: true, // Detect PII (SSNs, credit cards, etc.)
credentialScanning: true, // Detect API keys, passwords, tokens
humanApproval: 'high-risk', // 'all' | 'high-risk' | 'none'
blockedDomains: ['competitor.com'],
allowedDomains: ['company.com'],
maxRecipientsPerEmail: 50,
onApprovalRequired: async (email, risks) => {
console.log('Risks detected:', risks);
return confirm('Send anyway?');
},
},
});Automatic Scanning
When safety is configured, all outbound emails are automatically scanned:
await emai.emails.send({ to: '...', subject: '...', text: '...' });
// → Scans for PII, credentials, blocked domains before sending
// → If risks detected, triggers onApprovalRequired callbackManual Scanning
const result = emai.safety.scan(email);
// → {
// safe: false,
// risks: [
// { type: 'pii', severity: 'high', description: 'SSN detected', matched: '***-**-1234' }
// ],
// blocked: false,
// requiresApproval: true
// }What It Detects
| Type | Examples | |------|---------| | PII | Email addresses, phone numbers, SSNs, credit card numbers, dates of birth | | Credentials | API keys, passwords, private keys, JWT tokens, connection strings, AWS keys | | Phishing | Suspicious URLs, spoofed sender addresses, urgent payment requests | | Policy | Blocked domains, excessive recipients, custom policy violations |
Events & Real-Time
Events
Subscribe to email lifecycle events:
// Listen for specific events
emai.on('email:received', (email) => {
console.log(`New email from ${email.from.address}: ${email.subject}`);
});
emai.on('email:sent', (result) => {
console.log(`Email sent: ${result.id}`);
});
emai.on('safety:risk', ({ result }) => {
console.log(`Risks detected:`, result.risks);
});
emai.on('error', (err) => {
console.error('Error:', err.message);
});
// One-time listener
emai.once('email:received', (email) => { /* ... */ });
// Remove listener
const unsubscribe = emai.on('email:received', handler);
unsubscribe(); // or: emai.off('email:received', handler);All events:
| Event | Payload | Description |
|-------|---------|-------------|
| email:received | Email | New email arrived |
| email:sent | SendResult | Email sent successfully |
| email:read | { emailId } | Email marked as read |
| email:deleted | { emailId } | Email deleted |
| email:moved | { emailId, folder } | Email moved to folder |
| email:labeled | { emailId, label, action } | Label added/removed |
| email:classified | { emailId, result } | Email classified by AI |
| email:indexed | { emailId } | Email added to search index |
| safety:risk | { emailId?, result } | Security risk detected |
| safety:blocked | { emailId?, risks } | Email blocked by safety |
| safety:approved | { emailId? } | Email approved after risk review |
| watch:started | undefined | Watch started |
| watch:stopped | undefined | Watch stopped |
| watch:error | Error | Watch error occurred |
| error | Error | General error |
Watch
Monitor for new emails in real-time:
// Start watching (uses IMAP IDLE when available, falls back to polling)
await emai.watch.start({
folder: 'inbox',
pollInterval: 30000, // Poll every 30s (used when IDLE not available)
});
// Check status
emai.watch.isWatching(); // → true
// Stop watching
await emai.watch.stop();Webhooks
Forward events to external URLs:
const webhookId = emai.webhooks.register(
'https://your-app.com/webhook',
['email:received', 'email:sent'],
{
secret: 'your-webhook-secret',
headers: { 'X-Custom': 'value' },
retries: 3,
retryDelay: 1000,
timeout: 10000,
}
);
// List registered webhooks
const hooks = emai.webhooks.list();
// Remove a webhook
emai.webhooks.unregister(webhookId);MCP Server
Expose emai as an MCP server so AI agents (Claude, etc.) can use email tools directly:
npx emai mcpOr programmatically:
import { startEmaiMcpServer } from '@petter100/emai/mcp';
await startEmaiMcpServer({
provider: { type: 'imap', /* ... */ },
ai: { adapter: 'openai', apiKey: '...' },
});Available MCP Tools (37)
| Category | Tools |
|----------|-------|
| Email Reading | list_emails, get_email, get_thread, get_attachment |
| Email Sending | send_email, reply_to_email, forward_email, create_draft, update_draft, delete_draft |
| Email Management | mark_as_read, mark_as_unread, star_email, unstar_email, move_to_folder, delete_email, archive_email |
| Labels & Folders | list_labels, add_label, remove_label, create_label, delete_label, list_folders, create_folder |
| Search | search_semantic, search_fulltext, search_hybrid, index_emails |
| AI | classify_email, summarize_email, summarize_thread, extract_data, compose_email, compose_reply, prioritize_emails, detect_actions |
| Attachments | parse_attachment, ocr_attachment, describe_attachment |
| Safety | scan_email |
| Monitoring | start_watching, stop_watching |
CLI
emai includes a CLI for quick email operations:
npx emai init # Interactive setup — creates .emairc config
npx emai list # List recent emails
npx emai list --limit 20 --unread # Filter options
npx emai read <id> # Read email content
npx emai send --to [email protected] --subject "Hello" --body "Hi there"
npx emai reply <id> # Reply to an email
npx emai forward <id> --to [email protected]
npx emai search "invoices" --type semantic
npx emai search "from:john" --type fulltext
npx emai classify <id> # AI classification
npx emai summarize <id> # AI summary
npx emai star <id> # Star/unstar
npx emai delete <id> # Delete
npx emai move <id> --folder archive # Move to folder
npx emai watch # Watch for new emails (real-time)
npx emai mcp # Start MCP server
npx emai <command> --json # JSON output for any command
npx emai <command> --quiet # Suppress info messagesPlayground
emai ships with an interactive Playground — a web UI for testing every SDK feature against your real mailbox.
Getting Started
cd playground
npm install
npm run dev -- -p 4000Open http://localhost:4000 and enter your email provider credentials + AI adapter config.
Features
The Playground includes:
- Dashboard — One-click "Run All Tests" that exercises every SDK capability: email listing, classification, summarization, priority scoring, action detection, composition, extraction, search, and safety scanning
- Email Browser — Gmail-like UI with AI indicators (category, priority, urgency badges)
- AI Playground — Test individual AI features with custom parameters
- Attachment Viewer — Parse, OCR, and extract data from attachments with configurable extraction fields
- Search Console — Test semantic, full-text, and hybrid search with index management
- Event Stream — Real-time SSE viewer showing all SDK events as they happen
Advanced
Custom LLM Adapter
Implement the LLMAdapter interface to use any LLM:
import type { LLMAdapter, CompletionOptions } from '@petter100/emai';
import { z } from 'zod';
const myAdapter: LLMAdapter = {
name: 'my-llm',
async complete(prompt: string, options?: CompletionOptions): Promise<string> {
// Call your LLM and return the text response
const response = await myLlm.chat(prompt, options);
return response.text;
},
async completeJSON<T>(prompt: string, schema: z.ZodType<T>, options?: CompletionOptions): Promise<T> {
// Call your LLM and return parsed + validated JSON
const response = await myLlm.chat(prompt, { ...options, responseFormat: 'json' });
return schema.parse(JSON.parse(response.text));
},
async embed(texts: string[]): Promise<number[][]> {
// Generate embeddings for semantic search
return myEmbeddingModel.embed(texts);
},
async vision(images: Array<{ data: Buffer; mimeType: string }>, prompt: string): Promise<string> {
// Optional: describe images for attachment analysis
return myVisionModel.describe(images, prompt);
},
};
const emai = new Emai({
provider: { /* ... */ },
ai: { adapter: myAdapter },
});Custom Vector Store
Implement the VectorStore interface for any vector database:
import type { VectorStore, VectorEntry, VectorSearchResult } from '@petter100/emai';
const myStore: VectorStore = {
name: 'my-store',
async initialize(dimensions: number): Promise<void> {
// Create collection/index with the given vector dimensions
},
async upsert(vectors: VectorEntry[]): Promise<void> {
// Insert or update vectors
// Each entry: { id, vector: number[], metadata: Record<string, unknown>, content: string }
},
async search(vector: number[], limit: number, filter?: Record<string, unknown>): Promise<VectorSearchResult[]> {
// Find nearest neighbors
// Return: { id, score, metadata, content }[]
},
async delete(ids: string[]): Promise<void> {
// Remove vectors by ID
},
async count(): Promise<number> {
// Return total number of stored vectors
},
async close(): Promise<void> {
// Cleanup connections
},
};
const emai = new Emai({
provider: { /* ... */ },
search: { store: myStore },
});Storage
Persist email data locally for offline access:
// In-memory (default, no persistence)
storage: { type: 'memory' }
// SQLite (persistent)
storage: { type: 'sqlite', path: './emai-storage.db' }API Reference
Constructor
new Emai(config: EmaiConfig)| Property | Type | Required | Description |
|----------|------|----------|-------------|
| provider | ProviderConfig | Yes | Email provider configuration |
| ai | AiConfig | No | LLM adapter configuration (required for AI features) |
| search | SearchConfig | No | Vector store configuration (required for semantic search) |
| storage | StorageConfig | No | Local persistence configuration |
| safety | SafetyConfig | No | Security scanning configuration |
Connection
| Method | Returns | Description |
|--------|---------|-------------|
| connect() | Promise<void> | Establish provider connection |
| disconnect() | Promise<void> | Close all connections |
| isConnected() | boolean | Check connection status |
Emails (emai.emails.*)
| Method | Returns | Description |
|--------|---------|-------------|
| list(options?) | Promise<ListResult<Email>> | List emails with filtering |
| get(id) | Promise<Email> | Get a single email |
| send(options) | Promise<SendResult> | Send an email |
| reply(emailId, options) | Promise<SendResult> | Reply to an email |
| forward(emailId, options) | Promise<SendResult> | Forward an email |
| createDraft(options) | Promise<Email> | Create a draft |
| updateDraft(draftId, options) | Promise<Email> | Update a draft |
| deleteDraft(draftId) | Promise<void> | Delete a draft |
| markAsRead(emailId) | Promise<void> | Mark as read |
| markAsUnread(emailId) | Promise<void> | Mark as unread |
| star(emailId) | Promise<void> | Star an email |
| unstar(emailId) | Promise<void> | Unstar an email |
| moveToFolder(emailId, folder) | Promise<void> | Move to folder |
| archive(emailId) | Promise<void> | Archive an email |
| delete(emailId) | Promise<void> | Delete an email |
AI (emai.ai.*)
| Method | Returns | Description |
|--------|---------|-------------|
| classify(email) | Promise<ClassificationResult> | Classify a single email |
| classifyBatch(emails) | Promise<ClassificationResult[]> | Classify multiple emails |
| summarize(email) | Promise<SummaryResult> | Summarize a single email |
| summarizeThread(thread) | Promise<SummaryResult> | Summarize a thread |
| summarizeBatch(emails) | Promise<string> | Digest summary of multiple emails |
| extract(email, schema) | Promise<ExtractionResult<T>> | Extract structured data |
| compose(options) | Promise<ComposeResult> | Compose a new email |
| reply(email, options) | Promise<ComposeResult> | Compose a reply |
| rewriteInTone(text, tone) | Promise<string> | Rewrite text in a given tone |
| improveWriting(text) | Promise<string> | Improve writing quality |
| prioritize(email, context?) | Promise<PriorityResult> | Score email priority |
| prioritizeBatch(emails, context?) | Promise<Array<{ email, priority }>> | Batch prioritize |
| detectActions(email) | Promise<ActionItem[]> | Detect action items |
| detectActionsInThread(thread) | Promise<ActionItem[]> | Detect actions in thread |
Search (emai.search.*)
| Method | Returns | Description |
|--------|---------|-------------|
| semantic(query, options?) | Promise<SearchResult[]> | Semantic search |
| fullText(query, options?) | Promise<SearchResult[]> | Full-text keyword search |
| hybrid(query, options?) | Promise<SearchResult[]> | Combined search |
| index(emails) | Promise<void> | Index emails for search |
| indexEmail(email) | Promise<void> | Index a single email |
| removeFromIndex(emailId) | Promise<void> | Remove from index |
| getIndexedCount() | Promise<number> | Get indexed email count |
Attachments (emai.attachments.*)
| Method | Returns | Description |
|--------|---------|-------------|
| parse(attachment, options?) | Promise<ParsedAttachment> | Parse any attachment |
| toText(attachment, options?) | Promise<string> | Extract plain text |
| extract(attachment, schema) | Promise<ExtractionResult<T>> | Extract structured data |
| describe(attachment) | Promise<string> | Vision AI description |
| ocr(attachment) | Promise<string> | OCR text extraction |
| getContent(emailId, attachmentId) | Promise<Buffer> | Download content |
Safety (emai.safety.*)
| Method | Returns | Description |
|--------|---------|-------------|
| scan(email) | ScanResult | Scan an email for risks |
| scanOutbound(options) | Promise<ScanResult> | Scan before sending |
| checkBeforeSend(options) | Promise<{ allowed, result }> | Check + approval flow |
Labels & Folders
| Method | Returns | Description |
|--------|---------|-------------|
| labels.list() | Promise<Label[]> | List all labels |
| labels.add(emailId, label) | Promise<void> | Add label to email |
| labels.remove(emailId, label) | Promise<void> | Remove label from email |
| labels.create(name, color?) | Promise<Label> | Create a label |
| labels.delete(labelId) | Promise<void> | Delete a label |
| folders.list() | Promise<Folder[]> | List all folders |
| folders.create(name, parentId?) | Promise<Folder> | Create a folder |
| folders.delete(folderId) | Promise<void> | Delete a folder |
Threads (emai.threads.*)
| Method | Returns | Description |
|--------|---------|-------------|
| get(threadId) | Promise<Thread> | Get a thread |
| detect(emails) | Thread[] | Detect threads from emails |
Watch & Webhooks
| Method | Returns | Description |
|--------|---------|-------------|
| watch.start(options?) | Promise<void> | Start watching for emails |
| watch.stop() | Promise<void> | Stop watching |
| watch.isWatching() | boolean | Check watch status |
| webhooks.register(url, events, options?) | string | Register webhook |
| webhooks.unregister(webhookId) | void | Remove webhook |
| webhooks.list() | WebhookRegistration[] | List webhooks |
Events
| Method | Returns | Description |
|--------|---------|-------------|
| on(event, listener) | () => void | Subscribe (returns unsubscribe fn) |
| once(event, listener) | () => void | One-time subscription |
| off(event, listener) | void | Unsubscribe |
Optional Dependencies
emai keeps its core dependency-free. Install only what you need:
# AI adapters (pick one)
npm install openai # OpenAI
npm install @anthropic-ai/sdk # Anthropic
npm install @google/generative-ai # Google Gemini
npm install ollama # Ollama (local)
# Email providers
npm install googleapis # Gmail API
npm install @microsoft/microsoft-graph-client # Outlook
npm install imapflow nodemailer mailparser # IMAP/SMTP
# Vector stores (pick one)
npm install better-sqlite3 # SQLite
npm install pg # PostgreSQL + pgvector
npm install @pinecone-database/pinecone # Pinecone
npm install weaviate-client # Weaviate
npm install chromadb # ChromaDB
# Attachment processing
npm install pdf-parse # PDF parsing
npm install tesseract.js # OCR
npm install sharp # Image processing
npm install mammoth # Word documents
npm install papaparse # CSV parsing
# Other
npm install @modelcontextprotocol/sdk # MCP server
npm install commander # CLILicense
MIT
