
Apple Mail
- 1.4k installs
- Updated January 22, 2026
- rbouschery/marketplace
Helps with ai & agent building tasks.
About
apple-mail is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- apple-mail
- AI & Agent Building
- AI-coding skill
Apple Mail by the numbers
- 1,362 all-time installs (skills.sh)
- +1 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #862 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rbouschery/marketplace --skill apple-mailAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| Last updated | January 22, 2026 |
| Repository | rbouschery/marketplace ↗ |
What it does
Helps with ai & agent building tasks.
Files
Apple Mail Skill
This skill provides commands to interact with Apple Mail on macOS via AppleScript.
Available Scripts
All scripts are in the ../../scripts/ directory (relative to this file). Execute them via bash from the plugin root.
Account & Mailbox Management
| Script | Purpose | Arguments |
|---|---|---|
list-accounts.sh | List all email accounts | none |
list-mailboxes.sh | List mailboxes/folders | [account] (optional) |
get-unread-count.sh | Get unread email count | [account] [mailbox] (optional) |
Reading Emails
| Script | Purpose | Arguments |
|---|---|---|
get-emails.sh | Get recent emails | [account] [mailbox] [limit] [include_content] [unread_only] |
get-email-by-id.sh | Get specific email by ID | <id> [account] [mailbox] [include_content] |
search-emails.sh | Search emails | <query> [account] [mailbox] [limit] |
Sending & Composing
| Script | Purpose | Arguments |
|---|---|---|
send-email.sh | Send an email | <to> <subject> <body> [cc] [bcc] [from] |
create-draft.sh | Create a draft email | <subject> <body> [to] [cc] [bcc] [from] |
create-reply-draft.sh | Create reply to email | <message_id> <body> [reply_all] [account] [mailbox] |
send-draft.sh | Send front-most draft | none |
Email Management
| Script | Purpose | Arguments |
|---|---|---|
archive-email.sh | Archive an email | <message_id> [account] [mailbox] [archive_mailbox] |
delete-email.sh | Delete an email | <message_id> [account] [mailbox] |
mark-read.sh | Mark email as read | <message_id> [account] [mailbox] |
mark-unread.sh | Mark email as unread | <message_id> [account] [mailbox] |
Output Format
Scripts use delimiters for structured output:
<<>>separates fields within a record|||separates multiple recordsERROR:prefix indicates an error message
Email Record Format
id<<>>subject<<>>sender<<>>to<<>>cc<<>>bcc<<>>dateSent<<>>isRead<<>>content|||Usage Examples
List accounts
./scripts/list-accounts.shGet recent emails from INBOX
./scripts/get-emails.sh "" "INBOX" 10 false falseGet recent unread emails with content
./scripts/get-emails.sh "" "INBOX" 10 true trueGet specific email by ID
./scripts/get-email-by-id.sh 12345 "iCloud" "INBOX" trueSearch emails
./scripts/search-emails.sh "meeting notes" "" "" 20Send an email
./scripts/send-email.sh "recipient@example.com" "Subject" "Body text"Send with CC and BCC
./scripts/send-email.sh "to@example.com" "Subject" "Body" "cc@example.com" "bcc@example.com"Create a draft
./scripts/create-draft.sh "Draft Subject" "Draft body" "recipient@example.com"Reply to an email
./scripts/create-reply-draft.sh 12345 "Thanks for your message!" false "iCloud" "INBOX"Send the front-most draft
./scripts/send-draft.shArchive an email
./scripts/archive-email.sh 12345 "iCloud" "INBOX"Mark as read/unread
./scripts/mark-read.sh 12345 "iCloud" "INBOX"
./scripts/mark-unread.sh 12345 "iCloud" "INBOX"Parsing Output
When receiving email records, parse them like this:
1. Split by ||| to get individual records 2. Split each record by <<>> to get fields 3. Fields are: id, subject, sender, to, cc, bcc, dateSent, isRead, content
Example parsing in bash:
IFS='|||' read -ra emails <<< "$output"
for email in "${emails[@]}"; do
IFS='<<>>' read -ra fields <<< "$email"
id="${fields[0]}"
subject="${fields[1]}"
sender="${fields[2]}"
# ... etc
doneNotes
- Scripts require macOS with Apple Mail configured
- Apple Mail must have at least one account set up
- First run may trigger macOS permission prompts for automation
- Empty optional arguments should be passed as empty strings ""
- For scripts that need arrays (multiple recipients), pass comma-separated values
Reference
For advanced AppleScript patterns and customization, see ./reference/applescript-patterns.md.
AppleScript Patterns for Apple Mail
This document contains advanced AppleScript patterns for Apple Mail automation. Use these patterns when the provided scripts don't cover your specific use case.
Execution
All AppleScript code should be executed via osascript:
osascript <<'EOF'
tell application "Mail"
-- AppleScript code here
end tell
EOFOr with variable interpolation (use <<EOF without quotes):
osascript <<EOF
tell application "Mail"
set theMailbox to mailbox "$MAILBOX" of account "$ACCOUNT"
end tell
EOFOutput Delimiters
For structured output that's easy to parse:
<<>>separates fields within a record|||separates multiple recordsERROR:prefix indicates errors
Core Patterns
Accessing Accounts
tell application "Mail"
-- List all accounts
set accountList to {}
repeat with acc in accounts
set end of accountList to name of acc
end repeat
return accountList
end tellAccessing Mailboxes
tell application "Mail"
-- List mailboxes for specific account
set acc to account "iCloud"
set mailboxList to {}
repeat with mb in mailboxes of acc
set end of mailboxList to name of mb
end repeat
return mailboxList
end tellReferencing Mailboxes
-- By account and name
set theMailbox to mailbox "INBOX" of account "iCloud"
-- Just by name (first matching)
set theMailbox to mailbox "INBOX"
-- Special mailboxes
set theInbox to inbox -- Global inboxGetting Messages
tell application "Mail"
set theMailbox to mailbox "INBOX" of account "iCloud"
-- Get all messages
set allMsgs to messages of theMailbox
-- Get first N messages
set msgCount to count of messages of theMailbox
if msgCount > 10 then set msgCount to 10
repeat with i from 1 to msgCount
set msg to message i of theMailbox
-- process msg
end repeat
end tellFiltering Messages
tell application "Mail"
set theMailbox to mailbox "INBOX"
-- Unread messages only
set unreadMsgs to (messages of theMailbox whose read status is false)
-- Messages from specific sender
set fromMsgs to (messages of theMailbox whose sender contains "example@email.com")
-- Messages with subject containing text
set subjectMsgs to (messages of theMailbox whose subject contains "meeting")
-- Messages by date (newer than date)
set recentMsgs to (messages of theMailbox whose date sent > (current date) - 7 * days)
end tellMessage Properties
tell application "Mail"
set msg to message 1 of mailbox "INBOX"
-- Basic properties
set msgId to id of msg
set msgSubject to subject of msg
set msgSender to sender of msg
set msgDate to date sent of msg
set msgRead to read status of msg
set msgContent to content of msg
-- Recipients
set toRecipients to to recipients of msg
set ccRecipients to cc recipients of msg
set bccRecipients to bcc recipients of msg
-- Get email addresses from recipients
set toList to ""
repeat with r in to recipients of msg
set toList to toList & address of r & ","
end repeat
-- Remove trailing comma
if toList is not "" then set toList to text 1 thru -2 of toList
end tellFinding Message by ID
tell application "Mail"
set theMailbox to mailbox "INBOX"
set theMessage to (first message of theMailbox whose id is 12345)
end tellCreating and Sending Messages
tell application "Mail"
-- Create new message
set newMessage to make new outgoing message with properties {subject:"Hello", content:"Body text here", visible:true}
-- Add recipients
tell newMessage
make new to recipient at end of to recipients with properties {address:"to@example.com"}
make new cc recipient at end of cc recipients with properties {address:"cc@example.com"}
make new bcc recipient at end of bcc recipients with properties {address:"bcc@example.com"}
end tell
-- Send immediately
send newMessage
-- Or leave as draft (just don't call send)
end tellReplying to Messages
tell application "Mail"
set theMessage to message 1 of mailbox "INBOX"
-- Reply to sender only
set replyMsg to reply theMessage with opening window
-- Reply to all
set replyAllMsg to reply theMessage with opening window and reply to all
-- Set reply content
set content of replyMsg to "Thanks for your message!"
end tellForwarding Messages
tell application "Mail"
set theMessage to message 1 of mailbox "INBOX"
-- Forward message
set fwdMsg to forward theMessage with opening window
-- Add recipient
tell fwdMsg
make new to recipient at end of to recipients with properties {address:"forward@example.com"}
end tell
end tellMoving Messages
tell application "Mail"
set theMessage to message 1 of mailbox "INBOX"
set targetMailbox to mailbox "Archive" of account "iCloud"
move theMessage to targetMailbox
end tellDeleting Messages
tell application "Mail"
set theMessage to message 1 of mailbox "INBOX"
-- Move to trash
delete theMessage
-- Permanently delete (requires getting from trash first)
-- delete theMessage -- when already in trash
end tellMarking Messages
tell application "Mail"
set theMessage to message 1 of mailbox "INBOX"
-- Mark as read
set read status of theMessage to true
-- Mark as unread
set read status of theMessage to false
-- Flag/unflag
set flagged status of theMessage to true
set flagged status of theMessage to false
end tellUnread Count
tell application "Mail"
-- Specific mailbox
return unread count of mailbox "INBOX" of account "iCloud"
-- All mailboxes for account
set total to 0
repeat with mb in mailboxes of account "iCloud"
set total to total + (unread count of mb)
end repeat
return total
end tellSearching Messages
tell application "Mail"
set theMailbox to mailbox "INBOX"
set searchQuery to "meeting"
-- Search in subject, sender, and content
set foundMsgs to (messages of theMailbox whose subject contains searchQuery or sender contains searchQuery or content contains searchQuery)
-- Limit results
set maxResults to 10
set foundCount to count of foundMsgs
if foundCount > maxResults then set foundCount to maxResults
repeat with i from 1 to foundCount
set msg to item i of foundMsgs
-- process msg
end repeat
end tellWorking with Outgoing Messages (Drafts)
tell application "Mail"
-- Count drafts
set draftCount to count of outgoing messages
-- Get first draft
if draftCount > 0 then
set theDraft to outgoing message 1
-- Send it
send theDraft
end if
end tellError Handling
tell application "Mail"
try
set theMailbox to mailbox "INBOX" of account "NonexistentAccount"
-- ... operations
on error errMsg
return "ERROR:" & errMsg
end try
end tellString Escaping
When passing variables containing special characters:
-- Escape double quotes
set escapedText to "He said \"hello\""
-- Newlines in content
set bodyText to "Line 1" & return & "Line 2"
-- Or with escaped newlines from shell
set bodyText to "Line 1\nLine 2" -- won't work directly
set bodyText to "Line 1" & return & "Line 2" -- correctBuilding Output Strings
-- Concatenate with delimiters
set results to ""
repeat with msg in messages of mailbox "INBOX"
set msgId to id of msg
set msgSubject to subject of msg
set results to results & msgId & "<<>>" & msgSubject & "|||"
end repeat
return resultsCommon Issues and Solutions
Timeout Issues
Long operations may timeout. Increase timeout in shell:
osascript -e "..." & # Run in background
# or
timeout 60 osascript <<'EOF'
...
EOFPermission Errors
First run will prompt for Automation permissions in System Preferences > Security & Privacy > Privacy > Automation.
Account Names with Special Characters
Use exact account name as shown in Mail.app preferences.
Empty Results
Check that Mail.app is running and has accounts configured:
tell application "Mail"
if (count of accounts) is 0 then
return "ERROR:No email accounts configured"
end if
end tellFinding the Right Archive Mailbox
Different email providers use different names:
- Apple/iCloud: "Archive"
- Gmail: "All Mail"
- Outlook: "Archive"
- Generic: "Archives"
-- Try common archive names
set archiveMailbox to missing value
repeat with mb in mailboxes of theAccount
if name of mb is "Archive" or name of mb is "Archives" or name of mb is "All Mail" then
set archiveMailbox to mb
exit repeat
end if
end repeatAdvanced Patterns
Batch Operations
tell application "Mail"
set theMailbox to mailbox "INBOX"
set unreadMsgs to (messages of theMailbox whose read status is false)
-- Mark all as read
repeat with msg in unreadMsgs
set read status of msg to true
end repeat
end tellGetting Attachments Info
tell application "Mail"
set msg to message 1 of mailbox "INBOX"
set attachList to mail attachments of msg
repeat with att in attachList
set attName to name of att
set attSize to file size of att
-- Note: Saving attachments requires additional permissions
end repeat
end tellChecking Mail Status
tell application "Mail"
-- Check if Mail is running
if it is running then
-- Check connection status
repeat with acc in accounts
set accName to name of acc
-- Note: No direct way to check connection status
end repeat
end if
end tellIntegration Tips
1. Always use error handling - AppleScript can fail for many reasons 2. Use heredocs for complex scripts - Easier to read and maintain 3. Escape user input - Especially quotes and backslashes 4. Parse output carefully - Handle empty results and edge cases 5. Test with real data - Account names and mailbox names vary by provider
#!/bin/bash
# Archive an email by moving it to the Archive mailbox
# Usage: ./archive-email.sh <message_id> [account] [mailbox] [archive_mailbox]
# Output: Success or error message
MESSAGE_ID="${1:-}"
ACCOUNT="${2:-}"
MAILBOX="${3:-INBOX}"
ARCHIVE_MAILBOX="${4:-}"
if [ -z "$MESSAGE_ID" ]; then
echo "ERROR:Message ID is required"
exit 1
fi
# Build the account/mailbox part
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
# Build archive logic based on whether custom archive mailbox is specified
if [ -n "$ARCHIVE_MAILBOX" ]; then
ARCHIVE_LOGIC="
set archiveMailbox to missing value
repeat with mb in mailboxes of theAccount
if name of mb is \"$ARCHIVE_MAILBOX\" then
set archiveMailbox to mb
exit repeat
end if
end repeat
if archiveMailbox is missing value then
return \"ERROR:Archive mailbox '$ARCHIVE_MAILBOX' not found for this account\"
end if"
else
ARCHIVE_LOGIC='
-- Find the Archive mailbox for this account (try common names)
set archiveMailbox to missing value
repeat with mb in mailboxes of theAccount
if name of mb is "Archive" or name of mb is "Archives" or name of mb is "All Mail" then
set archiveMailbox to mb
exit repeat
end if
end repeat
if archiveMailbox is missing value then
return "ERROR:No Archive mailbox found for this account. Try specifying archive_mailbox parameter with the exact folder name."
end if'
fi
osascript <<EOF
tell application "Mail"
try
set theMailbox to $ACCOUNT_PART
set theMessage to (first message of theMailbox whose id is $MESSAGE_ID)
set theAccount to account of theMailbox
$ARCHIVE_LOGIC
move theMessage to archiveMailbox
return "Message archived successfully"
on error errMsg
return "ERROR:" & errMsg
end try
end tell
EOF
#!/bin/bash
# Create a draft email
# Usage: ./create-draft.sh <subject> <body> [to] [cc] [bcc] [from]
# For multiple recipients, pass comma-separated: "a@example.com,b@example.com"
# Output: Success or error message
SUBJECT="${1:-}"
BODY="${2:-}"
TO="${3:-}"
CC="${4:-}"
BCC="${5:-}"
FROM="${6:-}"
if [ -z "$SUBJECT" ]; then
echo "ERROR:Subject is required"
exit 1
fi
if [ -z "$BODY" ]; then
echo "ERROR:Body is required"
exit 1
fi
# Escape special characters for AppleScript
escape_for_applescript() {
echo "$1" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' '\r' | sed 's/\r/\\n/g'
}
ESCAPED_SUBJECT=$(escape_for_applescript "$SUBJECT")
ESCAPED_BODY=$(escape_for_applescript "$BODY")
# Build recipient commands
build_recipients() {
local TYPE="$1"
local ADDRESSES="$2"
local RESULT=""
IFS=',' read -ra ADDR_ARRAY <<< "$ADDRESSES"
for addr in "${ADDR_ARRAY[@]}"; do
addr=$(echo "$addr" | xargs) # trim whitespace
if [ -n "$addr" ]; then
RESULT="$RESULT
make new $TYPE recipient at end of $TYPE recipients with properties {address:\"$addr\"}"
fi
done
echo "$RESULT"
}
TO_RECIPIENTS=""
CC_RECIPIENTS=""
BCC_RECIPIENTS=""
FROM_PART=""
if [ -n "$TO" ]; then
TO_RECIPIENTS=$(build_recipients "to" "$TO")
fi
if [ -n "$CC" ]; then
CC_RECIPIENTS=$(build_recipients "cc" "$CC")
fi
if [ -n "$BCC" ]; then
BCC_RECIPIENTS=$(build_recipients "bcc" "$BCC")
fi
if [ -n "$FROM" ]; then
FROM_PART=", sender:\"$FROM\""
fi
osascript <<EOF
tell application "Mail"
set newMessage to make new outgoing message with properties {subject:"$ESCAPED_SUBJECT", content:"$ESCAPED_BODY", visible:true$FROM_PART}
tell newMessage
$TO_RECIPIENTS
$CC_RECIPIENTS
$BCC_RECIPIENTS
end tell
-- Draft is created by leaving the compose window open
return "Draft created successfully"
end tell
EOF
#!/bin/bash
# Create a draft reply to an existing email
# Usage: ./create-reply-draft.sh <message_id> <body> [reply_all] [account] [mailbox]
# Output: Success or error message
MESSAGE_ID="${1:-}"
BODY="${2:-}"
REPLY_ALL="${3:-false}"
ACCOUNT="${4:-}"
MAILBOX="${5:-INBOX}"
if [ -z "$MESSAGE_ID" ]; then
echo "ERROR:Message ID is required"
exit 1
fi
if [ -z "$BODY" ]; then
echo "ERROR:Reply body is required"
exit 1
fi
# Build the account/mailbox part
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
# Build reply command
if [ "$REPLY_ALL" = "true" ]; then
REPLY_COMMAND="reply theMessage with opening window and reply to all"
else
REPLY_COMMAND="reply theMessage with opening window"
fi
# Escape the body for AppleScript - handle quotes and newlines
ESCAPED_BODY=$(echo "$BODY" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' '\r' | sed 's/\r/" \& return \& "/g')
osascript -e "
tell application \"Mail\"
try
set theMailbox to $ACCOUNT_PART
set theMessage to (first message of theMailbox whose id is $MESSAGE_ID)
set replyMessage to $REPLY_COMMAND
-- Set the reply body content (the quoted original will appear below in the compose window)
set content of replyMessage to \"$ESCAPED_BODY\"
return \"Draft reply created successfully\"
on error errMsg
return \"ERROR:\" & errMsg
end try
end tell
"
#!/bin/bash
# Delete an email by moving it to the Trash mailbox
# Usage: ./delete-email.sh <message_id> [account] [mailbox]
# Output: Success or error message
MESSAGE_ID="${1:-}"
ACCOUNT="${2:-}"
MAILBOX="${3:-INBOX}"
if [ -z "$MESSAGE_ID" ]; then
echo "ERROR:Message ID is required"
exit 1
fi
# Build the account/mailbox part
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
osascript <<EOF
tell application "Mail"
try
set theMailbox to $ACCOUNT_PART
set theMessage to (first message of theMailbox whose id is $MESSAGE_ID)
delete theMessage
return "Message deleted successfully"
on error errMsg
return "ERROR:" & errMsg
end try
end tell
EOF
#!/bin/bash
# Get specific email(s) by ID
# Usage: ./get-email-by-id.sh <id> [account] [mailbox] [include_content]
# For multiple IDs, pass comma-separated: ./get-email-by-id.sh "123,456,789"
# Output: id<<>>subject<<>>sender<<>>to<<>>cc<<>>bcc<<>>dateSent<<>>isRead<<>>content|||...
IDS="${1:-}"
ACCOUNT="${2:-}"
MAILBOX="${3:-INBOX}"
INCLUDE_CONTENT="${4:-true}"
if [ -z "$IDS" ]; then
echo "ERROR:Message ID is required"
exit 1
fi
# Build the AppleScript dynamically
if [ "$INCLUDE_CONTENT" = "true" ]; then
CONTENT_PART='set msgContent to content of msg'
else
CONTENT_PART='set msgContent to ""'
fi
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
osascript <<EOF
tell application "Mail"
set results to ""
set targetIds to {$IDS}
try
set theMailbox to $ACCOUNT_PART
set allMessages to messages of theMailbox
repeat with targetId in targetIds
repeat with msg in allMessages
if id of msg is targetId then
set msgId to id of msg
set msgSubject to subject of msg
set msgSender to sender of msg
set msgDate to date sent of msg
set msgRead to read status of msg
$CONTENT_PART
-- Get recipients
set toList to ""
repeat with r in to recipients of msg
set toList to toList & address of r & ","
end repeat
if toList is not "" then set toList to text 1 thru -2 of toList
set ccList to ""
repeat with r in cc recipients of msg
set ccList to ccList & address of r & ","
end repeat
if ccList is not "" then set ccList to text 1 thru -2 of ccList
set bccList to ""
repeat with r in bcc recipients of msg
set bccList to bccList & address of r & ","
end repeat
if bccList is not "" then set bccList to text 1 thru -2 of bccList
set results to results & msgId & "<<>>" & msgSubject & "<<>>" & msgSender & "<<>>" & toList & "<<>>" & ccList & "<<>>" & bccList & "<<>>" & (msgDate as string) & "<<>>" & msgRead & "<<>>" & msgContent & "|||"
exit repeat -- Found the message, move to next ID
end if
end repeat
end repeat
on error errMsg
return "ERROR:" & errMsg
end try
return results
end tell
EOF
#!/bin/bash
# Get recent emails from a mailbox
# Usage: ./get-emails.sh [account] [mailbox] [limit] [include_content] [unread_only]
# Output: id<<>>subject<<>>sender<<>>to<<>>cc<<>>bcc<<>>dateSent<<>>isRead<<>>content|||...
ACCOUNT="${1:-}"
MAILBOX="${2:-INBOX}"
LIMIT="${3:-10}"
INCLUDE_CONTENT="${4:-false}"
UNREAD_ONLY="${5:-false}"
# Build the AppleScript dynamically
if [ "$INCLUDE_CONTENT" = "true" ]; then
CONTENT_PART='set msgContent to content of msg'
else
CONTENT_PART='set msgContent to ""'
fi
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
if [ "$UNREAD_ONLY" = "true" ]; then
MESSAGE_FILTER="(messages of theMailbox whose read status is false)"
else
MESSAGE_FILTER="messages of theMailbox"
fi
osascript <<EOF
tell application "Mail"
set results to ""
try
set theMailbox to $ACCOUNT_PART
set msgList to $MESSAGE_FILTER
set msgCount to count of msgList
if msgCount > $LIMIT then set msgCount to $LIMIT
repeat with i from 1 to msgCount
set msg to item i of msgList
set msgId to id of msg
set msgSubject to subject of msg
set msgSender to sender of msg
set msgDate to date sent of msg
set msgRead to read status of msg
$CONTENT_PART
-- Get recipients
set toList to ""
repeat with r in to recipients of msg
set toList to toList & address of r & ","
end repeat
if toList is not "" then set toList to text 1 thru -2 of toList
set ccList to ""
repeat with r in cc recipients of msg
set ccList to ccList & address of r & ","
end repeat
if ccList is not "" then set ccList to text 1 thru -2 of ccList
set bccList to ""
repeat with r in bcc recipients of msg
set bccList to bccList & address of r & ","
end repeat
if bccList is not "" then set bccList to text 1 thru -2 of bccList
set results to results & msgId & "<<>>" & msgSubject & "<<>>" & msgSender & "<<>>" & toList & "<<>>" & ccList & "<<>>" & bccList & "<<>>" & (msgDate as string) & "<<>>" & msgRead & "<<>>" & msgContent & "|||"
end repeat
on error errMsg
return "ERROR:" & errMsg
end try
return results
end tell
EOF
#!/bin/bash
# Get unread email count
# Usage: ./get-unread-count.sh [account] [mailbox]
# Output: Number of unread emails
ACCOUNT="${1:-}"
MAILBOX="${2:-}"
if [ -n "$ACCOUNT" ] && [ -n "$MAILBOX" ]; then
osascript <<EOF
tell application "Mail"
return unread count of mailbox "$MAILBOX" of account "$ACCOUNT"
end tell
EOF
elif [ -n "$ACCOUNT" ]; then
osascript <<EOF
tell application "Mail"
set total to 0
repeat with mb in mailboxes of account "$ACCOUNT"
set total to total + (unread count of mb)
end repeat
return total
end tell
EOF
elif [ -n "$MAILBOX" ]; then
osascript <<EOF
tell application "Mail"
set total to 0
repeat with acc in accounts
try
set total to total + (unread count of mailbox "$MAILBOX" of acc)
end try
end repeat
return total
end tell
EOF
else
osascript <<'EOF'
tell application "Mail"
set total to 0
repeat with acc in accounts
repeat with mb in mailboxes of acc
set total to total + (unread count of mb)
end repeat
end repeat
return total
end tell
EOF
fi
#!/bin/bash
# List all email accounts configured in Apple Mail
# Usage: ./list-accounts.sh
# Output: Comma-separated list of account names
osascript <<'EOF'
tell application "Mail"
set accountList to {}
repeat with acc in accounts
set end of accountList to name of acc
end repeat
return accountList
end tell
EOF
#!/bin/bash
# List mailboxes/folders for a specific account or all accounts
# Usage: ./list-mailboxes.sh [account]
# Output: account:mailbox1, mailbox2|||account2:mailbox1, mailbox2|||...
ACCOUNT="${1:-}"
if [ -n "$ACCOUNT" ]; then
osascript <<EOF
tell application "Mail"
set results to {}
try
set acc to account "$ACCOUNT"
set mailboxList to {}
repeat with mb in mailboxes of acc
set end of mailboxList to name of mb
end repeat
return mailboxList
on error
return {}
end try
end tell
EOF
else
osascript <<'EOF'
tell application "Mail"
set results to ""
repeat with acc in accounts
set accName to name of acc
set mailboxList to {}
repeat with mb in mailboxes of acc
set end of mailboxList to name of mb
end repeat
set results to results & accName & ":" & (mailboxList as string) & "|||"
end repeat
return results
end tell
EOF
fi
#!/bin/bash
# Mark an email as read
# Usage: ./mark-read.sh <message_id> [account] [mailbox]
# Output: Success or error message
MESSAGE_ID="${1:-}"
ACCOUNT="${2:-}"
MAILBOX="${3:-INBOX}"
if [ -z "$MESSAGE_ID" ]; then
echo "ERROR:Message ID is required"
exit 1
fi
# Build the account/mailbox part
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
osascript <<EOF
tell application "Mail"
try
set theMailbox to $ACCOUNT_PART
set theMessage to (first message of theMailbox whose id is $MESSAGE_ID)
set read status of theMessage to true
return "Message marked as read"
on error errMsg
return "ERROR:" & errMsg
end try
end tell
EOF
#!/bin/bash
# Mark an email as unread
# Usage: ./mark-unread.sh <message_id> [account] [mailbox]
# Output: Success or error message
MESSAGE_ID="${1:-}"
ACCOUNT="${2:-}"
MAILBOX="${3:-INBOX}"
if [ -z "$MESSAGE_ID" ]; then
echo "ERROR:Message ID is required"
exit 1
fi
# Build the account/mailbox part
if [ -n "$ACCOUNT" ]; then
ACCOUNT_PART="mailbox \"$MAILBOX\" of account \"$ACCOUNT\""
else
ACCOUNT_PART="mailbox \"$MAILBOX\""
fi
osascript <<EOF
tell application "Mail"
try
set theMailbox to $ACCOUNT_PART
set theMessage to (first message of theMailbox whose id is $MESSAGE_ID)
set read status of theMessage to false
return "Message marked as unread"
on error errMsg
return "ERROR:" & errMsg
end try
end tell
EOF
#!/bin/bash
# Search emails by query
# Usage: ./search-emails.sh <query> [account] [mailbox] [limit]
# Output: id<<>>subject<<>>sender<<>>to<<>>cc<<>>bcc<<>>dateSent<<>>isRead|||...
QUERY="${1:-}"
ACCOUNT="${2:-}"
MAILBOX="${3:-}"
LIMIT="${4:-10}"
if [ -z "$QUERY" ]; then
echo "ERROR:Search query is required"
exit 1
fi
# Escape double quotes in query
ESCAPED_QUERY=$(echo "$QUERY" | sed 's/"/\\"/g')
# Build mailbox selection part
if [ -n "$ACCOUNT" ] && [ -n "$MAILBOX" ]; then
MAILBOX_PART="{mailbox \"$MAILBOX\" of account \"$ACCOUNT\"}"
elif [ -n "$ACCOUNT" ]; then
MAILBOX_PART="mailboxes of account \"$ACCOUNT\""
elif [ -n "$MAILBOX" ]; then
MAILBOX_PART="{mailbox \"$MAILBOX\"}"
else
MAILBOX_PART="inbox"
fi
osascript <<EOF
tell application "Mail"
set results to ""
set foundCount to 0
set searchQuery to "$ESCAPED_QUERY"
try
set searchMailboxes to $MAILBOX_PART
repeat with mb in searchMailboxes
if foundCount >= $LIMIT then exit repeat
set msgList to (messages of mb whose subject contains searchQuery or sender contains searchQuery or content contains searchQuery)
repeat with msg in msgList
if foundCount >= $LIMIT then exit repeat
set msgId to id of msg
set msgSubject to subject of msg
set msgSender to sender of msg
set msgDate to date sent of msg
set msgRead to read status of msg
-- Get recipients
set toList to ""
repeat with r in to recipients of msg
set toList to toList & address of r & ","
end repeat
if toList is not "" then set toList to text 1 thru -2 of toList
set ccList to ""
repeat with r in cc recipients of msg
set ccList to ccList & address of r & ","
end repeat
if ccList is not "" then set ccList to text 1 thru -2 of ccList
set bccList to ""
repeat with r in bcc recipients of msg
set bccList to bccList & address of r & ","
end repeat
if bccList is not "" then set bccList to text 1 thru -2 of bccList
set results to results & msgId & "<<>>" & msgSubject & "<<>>" & msgSender & "<<>>" & toList & "<<>>" & ccList & "<<>>" & bccList & "<<>>" & (msgDate as string) & "<<>>" & msgRead & "|||"
set foundCount to foundCount + 1
end repeat
end repeat
on error errMsg
return "ERROR:" & errMsg
end try
return results
end tell
EOF
#!/bin/bash
# Send the front-most draft (compose window) in Apple Mail
# Usage: ./send-draft.sh
# Output: Success or error message
osascript <<'EOF'
tell application "Mail"
try
set outgoingCount to count of outgoing messages
if outgoingCount is 0 then
return "ERROR:No draft message found. Create a draft first using create-draft.sh or create-reply-draft.sh."
end if
set theDraft to outgoing message 1
send theDraft
return "Draft sent successfully"
on error errMsg
return "ERROR:" & errMsg
end try
end tell
EOF
#!/bin/bash
# Send an email
# Usage: ./send-email.sh <to> <subject> <body> [cc] [bcc] [from]
# For multiple recipients, pass comma-separated: "a@example.com,b@example.com"
# Output: Success or error message
TO="${1:-}"
SUBJECT="${2:-}"
BODY="${3:-}"
CC="${4:-}"
BCC="${5:-}"
FROM="${6:-}"
if [ -z "$TO" ]; then
echo "ERROR:Recipient (to) is required"
exit 1
fi
if [ -z "$SUBJECT" ]; then
echo "ERROR:Subject is required"
exit 1
fi
if [ -z "$BODY" ]; then
echo "ERROR:Body is required"
exit 1
fi
# Escape special characters for AppleScript
escape_for_applescript() {
echo "$1" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' '\r' | sed 's/\r/\\n/g'
}
ESCAPED_SUBJECT=$(escape_for_applescript "$SUBJECT")
ESCAPED_BODY=$(escape_for_applescript "$BODY")
# Build recipient commands
build_recipients() {
local TYPE="$1"
local ADDRESSES="$2"
local RESULT=""
IFS=',' read -ra ADDR_ARRAY <<< "$ADDRESSES"
for addr in "${ADDR_ARRAY[@]}"; do
addr=$(echo "$addr" | xargs) # trim whitespace
if [ -n "$addr" ]; then
RESULT="$RESULT
make new $TYPE recipient at end of $TYPE recipients with properties {address:\"$addr\"}"
fi
done
echo "$RESULT"
}
TO_RECIPIENTS=$(build_recipients "to" "$TO")
CC_RECIPIENTS=""
BCC_RECIPIENTS=""
FROM_PART=""
if [ -n "$CC" ]; then
CC_RECIPIENTS=$(build_recipients "cc" "$CC")
fi
if [ -n "$BCC" ]; then
BCC_RECIPIENTS=$(build_recipients "bcc" "$BCC")
fi
if [ -n "$FROM" ]; then
FROM_PART=", sender:\"$FROM\""
fi
osascript <<EOF
tell application "Mail"
set newMessage to make new outgoing message with properties {subject:"$ESCAPED_SUBJECT", content:"$ESCAPED_BODY"$FROM_PART}
tell newMessage
$TO_RECIPIENTS
$CC_RECIPIENTS
$BCC_RECIPIENTS
end tell
send newMessage
return "Message sent successfully"
end tell
EOF