apple-mail
Apple Mail
Interact with Mail.app via AppleScript and SQLite. Run scripts from: cd {baseDir}
Commands
| Command | Usage |
|---|---|
| Refresh | scripts/mail-refresh.sh [account] [wait_seconds] |
| List recent | scripts/mail-list.sh [mailbox] [account] [limit] |
| Search | scripts/mail-search.sh "query" [mailbox] [limit] |
| Fast search | scripts/mail-fast-search.sh "query" [limit] |
| Read email | scripts/mail-read.sh <message-id> [message-id...] |
| Delete | scripts/mail-delete.sh <message-id> [message-id...] |
| Mark read | scripts/mail-mark-read.sh <message-id> [message-id...] |
| Mark unread | scripts/mail-mark-unread.sh <message-id> [message-id...] |
| Send | scripts/mail-send.sh "to@email.com" "Subject" "Body" [from-account] [attachment] ¹ |
| Reply | scripts/mail-reply.sh <message-id> "body" [reply-all] |
| List accounts | scripts/mail-accounts.sh |
| List mailboxes | scripts/mail-mailboxes.sh [account] |
Refreshing Mail
Force Mail.app to check for new messages:
scripts/mail-refresh.sh # All accounts, wait up to 10s
scripts/mail-refresh.sh Google # Specific account only
scripts/mail-refresh.sh "" 5 # All accounts, max 5 seconds
scripts/mail-refresh.sh Google 0 # Google account, no wait
Smart sync detection:
- Script monitors database message count
- Returns early when sync completes (no changes for 2s)
- Reports new message count:
Sync complete in 2s (+3 messages)
Notes:
- Mail.app must be running (script will error if not)
mail-list.shdoes NOT auto-refresh — callmail-refresh.shfirst if you need fresh data
Output Format
List/search returns: ID | ReadStatus | Date | Sender | Subject
●= unread, blank = read
Gmail Mailboxes
⚠️ Gmail special folders need [Gmail]/ prefix:
| Shows as | Use |
|---|---|
Spam |
[Gmail]/Spam |
Sent Mail |
[Gmail]/Sent Mail |
All Mail |
[Gmail]/All Mail |
Trash |
[Gmail]/Trash |
Custom labels work without prefix.
Fast Search (SQLite)
✨ Now safe even if Mail.app is running — copies database to temp file first.
scripts/mail-fast-search.sh "query" [limit] # ~50ms vs minutes
Previously required Mail.app to be quit. Now works anytime by copying the database to a temp file before querying.
Performance Notes
Speed by operation:
| Operation | Speed | Notes |
|---|---|---|
mail-fast-search.sh |
~50ms | SQLite query, fastest |
mail-accounts.sh |
<1s | Simple AppleScript |
mail-list.sh |
1-3s | AppleScript, direct mailbox access |
mail-send.sh |
1-2s | Creates and sends message |
mail-read.sh |
~2s | Position-optimized lookup |
mail-delete.sh |
~0.5s | Position-optimized lookup |
mail-mark-*.sh |
~1.5s | Position-optimized lookup |
Optimization technique: SQLite provides account UUID and approximate message position. AppleScript jumps directly to that position instead of iterating from the start.
Batch operations supported:
mail-read.sh 123 456 789- Read multiple (separator between each)mail-delete.sh 123 456 789- Delete multiplemail-mark-read.sh 123 456- Mark multiple as readmail-mark-unread.sh 123 456- Mark multiple as unread
⚠️ No auto-refresh: Scripts read cached data. Call mail-refresh.sh first if you need latest emails.
Managing Emails
Delete emails:
scripts/mail-delete.sh 12345 # Delete one
scripts/mail-delete.sh 12345 12346 12347 # Delete multiple
Mark as read/unread:
scripts/mail-mark-read.sh 12345 12346 # Mark as read
scripts/mail-mark-unread.sh 12345 # Mark as unread
Bulk operations example:
# Find spam emails
scripts/mail-fast-search.sh "spam" 50 > spam.txt
# Extract IDs and delete them
grep "^[0-9]" spam.txt | cut -d'|' -f1 | xargs scripts/mail-delete.sh
Reading Email Bodies
scripts/mail-read.sh 12345 # Single email
scripts/mail-read.sh 12345 12346 12347 # Multiple emails (separated output)
Uses position-optimized lookup (~2s per message). Multiple emails are separated by ======== with a summary at the end.
Errors
| Error | Cause |
|---|---|
Mail.app is not running |
Open Mail.app before running scripts |
Account not found |
Invalid account — check mail-accounts.sh |
Message not found |
Invalid/deleted ID — get fresh from mail-list.sh |
Can't get mailbox |
Invalid name — check mail-mailboxes.sh |
Mail database not found |
SQLite DB missing — check ~/Library/Mail/V{9,10,11}/MailData/ |
Technical Details
Database: ~/Library/Mail/V{9,10,11}/MailData/Envelope Index
Message lookup method (optimized):
- Query SQLite for account UUID, mailbox path, and approximate position
- AppleScript accesses the specific account directly (no iteration)
- Search starts at the approximate position (±5 messages buffer)
- Falls back to full mailbox search only if position hint fails
Safety:
- Fast search copies database to temp file before querying
- Safe to use even if Mail.app is running
- Delete/read/mark operations query live database but access is minimal
Notes
- Message IDs are internal, get fresh ones from list/search
- Confirm recipient before sending
- AppleScript search is slow but comprehensive; SQLite is fast for metadata
- Delete/mark operations support bulk actions (pass multiple IDs)
- Always refresh before listing if you need the absolute latest emails
¹ Known limitation: Mail.app adds a leading blank line to sent emails. This is an AppleScript/Mail.app behavior that cannot be bypassed.