@appkit/apple-mail
v0.2.0
Published
TypeScript library for reading Apple Mail via SQLite
Maintainers
Readme
@appkit/apple-mail
TypeScript library for reading Apple Mail via SQLite database access.
Features
- 📖 Read-only access - Safe, no risk of corrupting your Mail database
- 🔍 Powerful queries - Search, filter, and query messages with flexible options
- 📦 Zero dependencies - Just better-sqlite3, zod, and debug
- 🎯 Type-safe - Complete TypeScript types for all entities
- 🚀 Fast - Synchronous SQLite access with better-sqlite3
- 🔌 Pluggable - Works in any Node.js service or script
- 🛡️ Multi-version support - Automatically detects Mail.app V7-V10
Installation
npm install @appkit/apple-mailRequirements
- Node.js >= 20.0.0
- macOS with Mail.app configured
- Full Disk Access permission (see setup below)
Setting up Full Disk Access
On macOS Catalina and later, you need to grant Full Disk Access:
- Open System Settings > Privacy & Security
- Scroll to Full Disk Access
- Click the + button
- Add your terminal app (Terminal.app, iTerm, etc.) or IDE (VS Code, etc.)
- Restart your terminal/IDE
Quick Start
import { AmailClient } from '@appkit/apple-mail';
const client = new AmailClient();
await client.connect();
// Get recent messages
const messages = await client.getMessages({ limit: 10 });
console.log(messages);
// Search messages
const results = await client.searchMessages('project update');
// Get unread count
const unread = await client.getUnreadCount();
client.disconnect();CLI Usage
The package includes a powerful CLI for managing Apple Mail from the command line.
Installation
Install globally for command-line access:
npm install -g @appkit/apple-mailOr use locally with npx:
npx @appkit/apple-mail checkQuick Start
Check setup and permissions:
amail checkList recent messages:
amail messages list --limit 10Search messages:
amail messages search "project update"Show message details:
amail messages show 12345List mailboxes:
amail mailboxes listCLI Commands
Global Options:
--json- Output as JSON (machine-readable)--limit <n>- Limit results (default: 50)--offset <n>- Skip N results--verbose- Show library debug logs--debug- Enable CLI debug logging--no-color- Disable colors--db-path <path>- Override database path
Commands:
amail check- Verify permissions and setupamail messages list [options]- List messages with filteringamail messages search <query> [options]- Search messagesamail messages show <id>- Show message detailsamail mailboxes list- List mailboxes
CLI Examples
Daily Email Management:
# Check unread messages from today
amail messages list --unread --since today
# Check all unread messages
amail messages list --unread
# View flagged/important messages
amail messages list --flagged
# Morning email check
amail messages list --unread --since yesterday --limit 20Search and Filter:
# Search messages from last week
amail messages search "invoice" --since 7d
# Find messages from specific sender
amail messages list --from [email protected] --since 30d
# Search in message body
amail messages search "meeting notes" --fields subject,body
# Find messages with specific keywords
amail messages search "urgent" --since 3dAdvanced Usage:
# Get JSON output for scripting
amail messages list --limit 5 --json | jq '.data[].subject'
# Export messages to file
amail messages list --since 30d --json > messages.json
# Count unread messages
amail messages list --unread --json | jq '.count'
# View specific message details
amail messages show 12345
# List all mailboxes
amail mailboxes listDate Filtering:
# Relative dates
amail messages list --since 1d # Last 24 hours
amail messages list --since 7d # Last week
amail messages list --since 1m # Last month
# Natural language
amail messages list --since today
amail messages list --since yesterday
# Specific dates
amail messages list --since 2026-02-01 --before 2026-02-11Combining Filters:
# Unread messages from specific sender this week
amail messages list --unread --from [email protected] --since 7d
# Flagged messages from last month
amail messages list --flagged --since 1m --limit 50
# Search unread messages
amail messages search "project" --since 7d | grep -i unreadFor complete CLI documentation, see docs/CLI.md.
API Overview
High-Level Convenience Methods
// Get messages with filtering
await client.getMessages({ limit: 10, isRead: false });
// Search messages
await client.searchMessages('meeting', { limit: 20 });
// Get specific message
await client.getMessageById(12345);
// Get mailboxes
await client.getMailboxes();
// Get unread count
await client.getUnreadCount();Service-Level Advanced Access
// Message operations
await client.messages.query({ sender: '[email protected]' });
await client.messages.getUnread({ limit: 50 });
await client.messages.getFlagged();
await client.messages.getByDateRange(startDate, endDate);
// Mailbox operations
await client.mailboxes.getByName('INBOX');
await client.mailboxes.getUnreadCount(mailboxId);
// Address operations
await client.addresses.getFrequentContacts(20);
await client.addresses.searchByEmail('john');
// Attachment operations
await client.attachments.getByMessageId(messageId);
await client.attachments.search('.pdf');Raw SQL Queries
const results = await client.executeRawQuery<{ count: number }>(
'SELECT COUNT(*) as count FROM messages WHERE read = ?',
[0]
);Usage Examples
Filter by Date Range
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-01-31');
const messages = await client.messages.getByDateRange(startDate, endDate, {
limit: 100
});Get Messages with Attachments
const withAttachments = await client.getMessages({
hasAttachments: true,
limit: 20
});
// Get attachment details
for (const message of withAttachments) {
const attachments = await client.attachments.getByMessageId(message.id);
console.log(`${message.subject} has ${attachments.length} attachments`);
}Complex Query
const messages = await client.messages.query({
sender: '[email protected]',
dateRange: {
start: new Date('2024-11-01'),
end: new Date('2024-11-30')
},
isRead: false,
hasAttachments: true,
orderBy: 'date',
orderDirection: 'desc',
limit: 50
});Get Frequent Contacts
const contacts = await client.addresses.getFrequentContacts(20);
contacts.forEach(contact => {
console.log(`${contact.email} - ${contact.messageCount} messages`);
});Configuration Options
const client = new AmailClient({
databasePath: '/custom/path/to/Envelope Index', // Optional: override default
readOnly: true, // Always true (safety)
timeout: 5000, // Query timeout in ms
logLevel: 'info' // debug, info, warn, error
});Debugging
Enable debug logging:
DEBUG=amail:* npm run inspectUse the included debug scripts:
# Inspect database and show statistics
npm run inspect
# Test various queries
npm run queryType Definitions
Message
interface Message {
id: number;
subject: string;
sender: Address;
recipients: Address[];
dateSent: Date;
dateReceived: Date;
mailboxId: number;
mailboxName: string;
isRead: boolean;
isFlagged: boolean;
hasAttachments: boolean;
attachmentCount: number;
metadata: {
messageId: string;
inReplyTo?: string;
references?: string[];
};
}Mailbox
interface Mailbox {
id: number;
name: string;
accountId: number;
accountName: string;
type: MailboxType; // inbox, sent, drafts, trash, etc.
unreadCount?: number;
totalCount?: number;
}Development
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Watch tests
npm run test:watch
# Type check
npm run typecheck
# Inspect database
npm run inspect
# Test queries
npm run queryArchitecture
src/
├── core/ # Database connection and query builder
├── services/ # Message, Mailbox, Address, Attachment services
├── types/ # TypeScript type definitions
├── utils/ # Utilities (logging, permissions, validators)
└── index.ts # Main AmailClient classLimitations
- Read-only - Cannot modify, delete, or send messages
- Metadata only - Cannot read message body content (requires parsing .emlx files)
- No file access - Attachment metadata only (no file content access)
- macOS only - Requires Mail.app and SQLite database
Security & Privacy
- ✅ Read-only access (no write operations)
- ✅ Local processing only (no external communication)
- ✅ No telemetry or tracking
- ✅ Your email data never leaves your machine
Troubleshooting
Database locked error
If you get SQLITE_BUSY errors:
- Mail.app may be performing indexing
- Try quitting Mail.app temporarily
- Increase timeout in options
Permission denied
- Ensure Full Disk Access is granted
- Restart terminal/IDE after granting permission
- Run
npm run inspectto verify permissions
Database not found
- Ensure Mail.app has been run at least once
- Check that Mail.app is configured with at least one account
License
MIT
Author
Dave Weaver [email protected]
