@appkit/apple-mail
v0.1.7
Published
TypeScript library for reading Apple Mail via SQLite
Downloads
29
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();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]
