npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@appkit/apple-mail

v0.1.7

Published

TypeScript library for reading Apple Mail via SQLite

Downloads

29

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-mail

Requirements

  • 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:

  1. Open System Settings > Privacy & Security
  2. Scroll to Full Disk Access
  3. Click the + button
  4. Add your terminal app (Terminal.app, iTerm, etc.) or IDE (VS Code, etc.)
  5. 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 inspect

Use the included debug scripts:

# Inspect database and show statistics
npm run inspect

# Test various queries
npm run query

Type 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 query

Architecture

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 class

Limitations

  • 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 inspect to 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]