
Automating Mail
- 18 installs
- 39 repo stars
- Updated January 14, 2026
- spillwavesolutions/automating-mac-apps-plugin
Helps with ai & agent building tasks during AI-assisted development.
About
automating-mail is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- automating-mail
- AI & Agent Building
- AI-coding skill
Automating Mail by the numbers
- 18 all-time installs (skills.sh)
- Ranked #10,710 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/spillwavesolutions/automating-mac-apps-plugin --skill automating-mailAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 39 |
| Last updated | January 14, 2026 |
| Repository | spillwavesolutions/automating-mac-apps-plugin ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Automating Apple Mail (JXA-first, AppleScript discovery)
Contents
- Relationship to macOS automation skill
- Core Framing
- Workflow
- Quick Start Examples
- Validation Checklist
- When Not to Use
- What to load
Relationship to the macOS automation skill
- Use
automating-mac-appsfor permissions, shell, and UI scripting guidance. - PyXA Installation: See
automating-mac-appsskill (PyXA Installation section).
Core Framing
- Mail dictionary is AppleScript-first; discover in Script Editor.
- Objects are specifiers: read via method calls (
message.subject()), modify via assignments (message.readStatus = true). - ObjC bridge available for advanced filesystem operations.
Workflow (default)
1) [ ] Ensure Mail configured and automation permissions enabled. 2) [ ] Discover terms in Script Editor (Mail dictionary). 3) [ ] Prototype minimal AppleScript command. 4) [ ] Port to JXA with defensive checks. 5) [ ] Use batch reads for performance. 6) [ ] Use UI scripting for signature and UI-only actions.
Quick Start Examples
Read inbox (JXA):
const Mail = Application('Mail');
const message = Mail.inbox.messages[0];
console.log(message.subject());Compose message (JXA):
const msg = Mail.OutgoingMessage({
subject: "Status Update",
content: "All systems go."
});
Mail.outgoingMessages.push(msg);
msg.visible = true;PyXA alternative:
import PyXA
mail = PyXA.Mail()
inbox = mail.inboxes()[0]
message = inbox.messages()[0]
print(f"Subject: {message.subject()}")Validation Checklist
- [ ] Automation permissions granted (System Settings > Privacy > Automation)
- [ ] Inbox access works:
Mail.inbox.messages.length - [ ] Message property reads return expected values
- [ ] Composition creates visible draft
- [ ] Batch operations complete without errors
When Not to Use
- For cross-platform email automation (use IMAP/SMTP libraries)
- For bulk email sending (use transactional email services like SendGrid)
- When processing untrusted email content (security risk)
- For non-macOS platforms
What to load
- Mail JXA basics:
automating-mail/references/mail-basics.md - Recipes (filter, move, compose):
automating-mail/references/mail-recipes.md - Advanced patterns (batch ops, HTML, signatures):
automating-mail/references/mail-advanced.md - Dictionary translation table:
automating-mail/references/mail-dictionary.md - Rule scripts:
automating-mail/references/mail-rules.md - HTML + signature workflow:
automating-mail/references/html-signature-workflow.md - Attachment extraction pipeline:
automating-mail/references/attachment-extraction.md - Mailbox archiver:
automating-mail/references/mailbox-archiver.md - HTML data merge:
automating-mail/references/html-data-merge.md - PyXA API Reference (complete class/method docs):
automating-mail/references/mail-pyxa-api-reference.md
Attachment extraction pipeline
Save attachments from selected messages
const Mail = Application("Mail");
const app = Application.currentApplication();
app.includeStandardAdditions = true;
const outDir = "/Users/you/Downloads/attachments";
const sel = Mail.selection();
sel.forEach(m => {
m.mailAttachments().forEach(a => {
const path = outDir + "/" + a.name();
Mail.save(a, { in: path });
});
});Notes:
- Prefer a user-writable directory (Documents/Downloads).
- For errors, fall back to shell copy if the attachment provides a fileName path.
HTML data merge
Merge JSON data into HTML template
const Mail = Application("Mail");
function renderTemplate(tpl, data) {
return tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => data[k] ?? "");
}
const template = "<html><body><h1>Report for {{name}}</h1><p>Total: {{total}}</p></body></html>";
const data = { name: "Acme", total: "$1,234" };
const msg = Mail.OutgoingMessage({ subject: "HTML Report", visible: false });
Mail.outgoingMessages.push(msg);
msg.htmlContent = renderTemplate(template, data);
msg.visible = true;HTML + signature workflow
Pattern
1) Create message with visible: false. 2) Set htmlContent. 3) Make visible and set signature via UI scripting.
const Mail = Application("Mail");
const msg = Mail.OutgoingMessage({ subject: "Update", visible: false });
Mail.outgoingMessages.push(msg);
msg.htmlContent = "<html><body><h1>Status</h1></body></html>";
msg.visible = true;
// UI scripting signature selection (outline)
const se = Application("System Events");
const proc = se.processes.byName("Mail");
// find signature popup button, select signatureMail JXA advanced patterns
Batch property reads
JXA:
const msgs = Mail.inbox.messages;
const ids = msgs.id();
const dates = msgs.dateReceived();PyXA:
import PyXA
mail = PyXA.Application("Mail")
inbox = mail.inboxes()[0] # Get first inbox
messages = inbox.messages()
# Batch read properties (efficient)
ids = messages.id()
dates = messages.date_received()
subjects = messages.subject()HTML composition (invisible)
JXA:
const htmlMsg = Mail.OutgoingMessage({ subject: "HTML", visible: false });
Mail.outgoingMessages.push(htmlMsg);
htmlMsg.htmlContent = "<html><body><h1>Status</h1></body></html>";
htmlMsg.visible = true;PyXA:
import PyXA
mail = PyXA.Application("Mail")
# Create HTML message (invisible initially)
html_message = mail.outgoing_messages().push({
"subject": "HTML Status Report",
"content": "", # Will be replaced with HTML
"visible": False
})
# Set HTML content
html_content = "<html><body><h1>Status Report</h1><p>All systems operational.</p></body></html>"
html_message.content = html_content
# Make visible for editing/sending
html_message.visible = TrueSignature via UI scripting (outline)
JXA:
const se = Application("System Events");
const mailProc = se.processes.byName("Mail");
// Locate signature popup and choose item (UI hierarchy varies)PyXA:
import PyXA
# For signature selection (UI scripting approach)
system_events = PyXA.Application("System Events")
mail_process = system_events.processes().by_name("Mail")
# Access signature popup menu
# Note: UI hierarchy varies by Mail version
compose_window = mail_process.windows()[0] # Main compose window
signature_menu = compose_window.pop_up_buttons().by_name("Signature")
signature_menu.click()
# Select specific signature
menu_items = system_events.processes().by_name("Mail").menus()[0].menu_items()
signature_item = menu_items.by_title("Work Signature")
signature_item.click()Recursive mailbox search
JXA:
function findMailbox(container, name) {
const kids = container.mailboxes();
for (let i = 0; i < kids.length; i++) {
if (kids[i].name() === name) return kids[i];
}
for (let i = 0; i < kids.length; i++) {
const found = findMailbox(kids[i], name);
if (found) return found;
}
return null;
}PyXA:
import PyXA
def find_mailbox(container, name):
"""Recursively search for a mailbox by name"""
# Check direct children first
mailboxes = container.mailboxes()
for mailbox in mailboxes:
if mailbox.name == name:
return mailbox
# Recursively search child mailboxes
for mailbox in mailboxes:
found = find_mailbox(mailbox, name)
if found:
return found
return None
# Usage
mail = PyXA.Application("Mail")
account = mail.accounts()[0] # First account
target_mailbox = find_mailbox(account, "Archive/Sent Items")
if target_mailbox:
print(f"Found mailbox: {target_mailbox.name}")Mail Automation Basics
This document shows equivalent patterns in JXA (JavaScript for Automation), PyXA (Python for Apple Automation), and PyObjC (Python-Objective-C bridge) for basic Mail.app operations.
Bootstrapping
JXA
const Mail = Application("Mail");
Mail.activate();PyXA
import PyXA
mail = PyXA.Application("Mail")
mail.activate()PyObjC
from objc import *
import Mail
mail_app = Mail.sharedApplication()
mail_app.activate()Selected Messages
JXA
const sel = Mail.selection();
const subjects = sel.map(m => m.subject());PyXA
selection = mail.selection()
subjects = [msg.subject for msg in selection]PyObjC
selection = mail_app.selection()
subjects = [msg.subject() for msg in selection]Open Mailbox by Name
JXA
const acct = Mail.accounts.byName("iCloud");
const inbox = acct.mailboxes.byName("INBOX");PyXA
account = mail.accounts().byName("iCloud")
inbox = account.mailboxes().byName("INBOX")PyObjC
accounts = mail_app.accounts()
account = next(acc for acc in accounts if acc.displayName() == "iCloud")
mailboxes = account.mailboxes()
inbox = next(mb for mb in mailboxes if mb.name() == "INBOX")Mail dictionary translation table
AppleScript JXA
----------------------------------- ------------------------------------------
selection Mail.selection()
subject of message msg.subject()
flagged status msg.flaggedStatus()
read status msg.readStatus()
mailbox "INBOX" account.mailboxes.byName("INBOX")
make new outgoing message Mail.OutgoingMessage({ ... }) -> push()Notes:
- Use batch reads on collections (messages.subject()).
- Use Path() for attachment file names.
PyXA Mail Module API Reference
New in PyXA version 0.0.4 - Control macOS Mail.app using JXA-like syntax from Python.
This reference documents all classes, methods, properties, and enums in the PyXA Mail module. For practical examples and usage patterns, see mail-basics.md and mail-recipes.md.
Contents
- Class Hierarchy
- XAMailApplication
- XAMailAccount
- XAMailbox
- XAMailMessage
- XAMailOutgoingMessage
- XAMailRecipient Classes
- XAMailAttachment
- XAMailHeader
- XAMailRule
- XAMailSignature
- XAMailSMTPServer
- XAMailMessageViewer
- Account Type Classes
- List Classes
- Enumerations
- Quick Reference Tables
---
Class Hierarchy
XAObject
├── XAMailApplication (XASBApplication)
│ ├── XAMailAccount
│ │ ├── XAMailIMAPAccount
│ │ ├── XAMailPOPAccount
│ │ └── XAMailICloudAccount
│ ├── XAMailbox
│ │ └── XAMailContainer
│ ├── XAMailMessage
│ ├── XAMailOutgoingMessage
│ ├── XAMailRecipient
│ │ ├── XAMailToRecipient
│ │ ├── XAMailCcRecipient
│ │ └── XAMailBccRecipient
│ ├── XAMailAttachment
│ ├── XAMailHeader
│ ├── XAMailRule
│ │ └── XAMailRuleCondition
│ ├── XAMailSignature
│ ├── XAMailSMTPServer
│ ├── XAMailMessageViewer
│ ├── XAMailDocument
│ └── XAMailWindow (XASBWindow)---
XAMailApplication
Bases: XASBApplication
Main entry point for interacting with Mail.app.
Properties
| Property | Type | Description |
|---|---|---|
name | str | The name of the application |
version | str | The version number of Mail.app |
application_version | str | The build number of Mail.app |
frontmost | bool | Whether Mail is the active application |
background_activity_count | int | Number of background activities running |
primary_email | str | The user's primary email address |
selection | XAMailMessageList | Currently selected messages |
Mailbox Properties
| Property | Type | Description |
|---|---|---|
inbox | XAMailbox | The top-level inbox |
drafts_mailbox | XAMailbox | The top-level drafts mailbox |
sent_mailbox | XAMailbox | The top-level sent mailbox |
trash_mailbox | XAMailbox | The top-level trash mailbox |
junk_mailbox | XAMailbox | The top-level junk mailbox |
outbox | XAMailbox | The top-level outbox |
Composition Settings
| Property | Type | Description |
|---|---|---|
always_bcc_myself | bool | Include user in Bcc field |
always_cc_myself | bool | Include user in Cc field |
default_message_format | Format | Default format for new messages |
quote_original_message | bool | Include original text in replies |
include_all_original_message_text | bool | Quote all or selected text |
same_reply_format | bool | Reply in same format as original |
expand_group_addresses | bool | Expand group addresses |
choose_signature_when_composing | bool | Allow signature choice in compose |
selected_signature | str | Currently selected signature name |
Fetch Settings
| Property | Type | Description |
|---|---|---|
fetches_automatically | bool | Auto-fetch mail at interval |
fetch_interval | int | Minutes between fetches (-1 = auto) |
download_html_attachments | bool | Download HTML images/attachments |
Display Settings
| Property | Type | Description |
|---|---|---|
message_font | str | Font name for messages |
message_font_size | float | Font size for messages |
message_list_font | str | Font for message list |
message_list_font_size | float | Font size for message list |
fixed_width_font | str | Font for plain text |
fixed_width_font_size | int | Font size for plain text |
use_fixed_width_font | bool | Use fixed-width for plain text |
color_quoted_text | bool | Color quoted text |
level_one_quoting_color | QuotingColor | Color for level 1 quotes |
level_two_quoting_color | QuotingColor | Color for level 2 quotes |
level_three_quoting_color | QuotingColor | Color for level 3 quotes |
highlight_selected_conversation | bool | Highlight conversation messages |
check_spelling_while_typing | bool | Auto spell-check |
new_mail_sound | str | Sound for new mail ("None" to disable) |
should_play_other_mail_sounds | bool | Play other sounds |
Methods
accounts(filter=None) → XAMailAccountList
Returns mail accounts matching the filter.
imap_accounts(filter=None) → XAMailIMAPAccountList
Returns IMAP accounts matching the filter.
pop_accounts(filter=None) → XAMailPOPAccountList
Returns POP accounts matching the filter.
mailboxes(filter=None) → XAMailboxList
Returns mailboxes matching the filter.
outgoing_messages(filter=None) → XAMailOutgoingMessageList
Returns outgoing messages matching the filter.
message_viewers(filter=None) → XAMailMessageViewerList
Returns message viewer windows matching the filter.
rules(filter=None) → XAMailRuleList
Returns mail rules matching the filter.
signatures(filter=None) → XAMailSignatureList
Returns signatures matching the filter.
smtp_servers(filter=None) → XAMailSMTPServerList
Returns SMTP servers matching the filter.
check_for_new_mail(account) → XAMailApplication
Checks for new mail in the specified account.
synchronize(account) → XAMailApplication
Synchronizes the specified account.
import_mailbox(file_path) → XAMailApplication
Imports a mailbox from the specified file path.
---
XAMailAccount
Bases: XAObject
Represents a mail account (base class for IMAP, POP, iCloud).
Properties
| Property | Type | Description |
|---|---|---|
id | str | Unique identifier |
name | str | Account name |
account_type | AccountType | Type: pop, smtp, imap, or iCloud |
enabled | bool | Whether account is enabled |
user_name | str | Username for connection |
password | None | Password (write-only) |
full_name | str | User's full name |
email_addresses | list[str] | Associated email addresses |
server_name | str | Host name for connection |
port | int | Connection port |
uses_ssl | bool | SSL enabled |
authentication | AuthenticationMethod | Authentication scheme |
account_directory | str | Storage directory on disk |
delivery_account | XAMailSMTPServer | SMTP server for sending |
move_deleted_messages_to_trash | bool | Move deleted to trash |
empty_trash_on_quit | bool | Delete trash on quit |
empty_trash_frequency | int | Days before trash deletion (0=on quit, -1=never) |
empty_junk_messages_on_quit | bool | Delete junk on quit |
empty_junk_messages_frequency | int | Days before junk deletion |
Methods
mailboxes(filter=None) → XAMailboxList
Returns mailboxes for this account.
---
XAMailbox
Bases: XAObject
Represents a mailbox (folder) in Mail.app.
Properties
| Property | Type | Description |
|---|---|---|
name | str | Mailbox name |
unread_count | int | Number of unread messages |
account | XAMailAccount | Parent account |
container | XAMailbox | Parent mailbox (if nested) |
Methods
messages(filter=None) → XAMailMessageList
Returns messages in this mailbox.
mailboxes(filter=None) → XAMailboxList
Returns nested mailboxes.
delete()
Permanently deletes the mailbox.
---
XAMailMessage
Bases: XAObject
Represents an email message.
Properties
| Property | Type | Description |
|---|---|---|
id | int | Unique identifier |
message_id | int | Unique message ID string |
subject | str | Subject line |
sender | str | Sender's address |
reply_to | str | Reply-to address |
content | XAText | Message contents |
source | str | Raw message source |
all_headers | str | All headers as string |
date_sent | datetime | Date/time sent |
date_received | datetime | Date/time received |
message_size | int | Size in bytes |
mailbox | XAMailbox | Containing mailbox |
background_color | HighlightColor | Background highlight color |
Status Properties
| Property | Type | Description |
|---|---|---|
read_status | bool | Whether read |
flagged_status | bool | Whether flagged |
flag_index | int | Flag index (-1 = not flagged) |
deleted_status | bool | Whether deleted |
junk_mail_status | bool | Whether marked as junk |
was_replied_to | bool | Whether replied to |
was_forward | bool | Whether forwarded |
was_redirected | bool | Whether redirected |
Methods
open() → XAMailMessage
Opens the message in a separate window.
delete()
Permanently deletes the message.
forward(open_window=True) → XAMailOutgoingMessage
Creates a forward of the message.
reply(open_window=True, reply_all=False) → XAMailOutgoingMessage
Creates a reply to the message.
redirect(open_window=True) → XAMailOutgoingMessage
Creates a redirect of the message.
Recipient Methods
| Method | Returns | Description |
|---|---|---|
to_recipients(filter=None) | XAMailToRecipientList | Primary recipients |
cc_recpients(filter=None) | XAMailCcRecipientList | CC recipients |
bcc_recipients(filter=None) | XAMailBccRecipientList | BCC recipients |
recipients(filter=None) | XAMailRecipientList | All recipients |
headers(filter=None) | XAMailHeaderList | Message headers |
mail_attachments(filter=None) | XAMailAttachmentList | Attachments |
---
XAMailOutgoingMessage
Bases: XAObject
Represents an outgoing (draft) message.
Properties
| Property | Type | Description |
|---|---|---|
id | int | Unique identifier |
subject | str | Subject line |
sender | str | Sender address |
content | XAText | Message contents |
message_signature | XAMailSignature | Message signature |
visible | bool | Whether window is shown |
Methods
send() → bool
Sends the message. Returns success status.
save()
Saves the message as a draft.
delete()
Permanently deletes the outgoing message.
close(save=SaveOption.YES)
Closes the message window.
---
XAMailRecipient Classes
XAMailRecipient (Base)
| Property | Type | Description |
|---|---|---|
name | str | Display name |
address | str | Email address |
XAMailToRecipient
Primary (To:) recipient. Inherits from XAMailRecipient.
XAMailCcRecipient
CC recipient. Inherits from XAMailRecipient.
XAMailBccRecipient
BCC recipient. Inherits from XAMailRecipient.
---
XAMailAttachment
Bases: XAObject
Represents a message attachment.
Properties
| Property | Type | Description |
|---|---|---|
id | str | Unique identifier |
name | str | Attachment filename |
mime_type | str | MIME type (e.g., "text/plain") |
file_size | int | Size in bytes |
downloaded | bool | Whether downloaded |
Methods
delete()
Permanently deletes the attachment.
---
XAMailHeader
Bases: XAObject
Represents a message header.
Properties
| Property | Type | Description |
|---|---|---|
name | str | Header name |
content | str | Header value |
---
XAMailRule
Bases: XAObject
Represents a mail filtering rule.
Properties
| Property | Type | Description |
|---|---|---|
name | str | Rule name |
enabled | bool | Whether enabled |
all_conditions_must_be_met | bool | AND vs OR conditions |
stop_evaluating_rules | bool | Stop after match |
Action Properties
| Property | Type | Description |
|---|---|---|
delete_message | bool | Delete matching messages |
mark_read | bool | Mark as read |
mark_flagged | bool | Mark as flagged |
mark_flag_index | int | Flag index (-1 = disabled) |
color_message | HighlightColor | Apply color |
highlight_text_using_color | bool | Color text vs background |
move_message | XAMailbox | Move to mailbox |
copy_message | XAMailbox | Copy to mailbox |
should_move_message | bool | Has move action |
should_copy_message | bool | Has copy action |
forward_message | str | Forward addresses (comma-separated) |
forward_text | str | Prepend text for forward |
redirect_message | str | Redirect addresses |
reply_text | str | Auto-reply text |
run_script | str | AppleScript file path |
play_sound | str | Sound name or path |
Methods
rule_conditions(filter=None) → XAMailRuleConditionList
Returns conditions for this rule.
delete()
Permanently deletes the rule.
---
XAMailRuleCondition
Bases: XAObject
Represents a condition within a mail rule.
Properties
| Property | Type | Description |
|---|---|---|
rule_type | RuleType | Type of condition |
qualifier | RuleQualifier | Comparison qualifier |
expression | str | Expression to match |
header | str | Header key (for header rules) |
Methods
delete()
Permanently deletes the rule condition.
---
XAMailSignature
Bases: XAObject
Represents an email signature.
Properties
| Property | Type | Description |
|---|---|---|
name | str | Signature name |
content | XAText | Signature content |
Methods
delete()
Permanently deletes the signature.
---
XAMailSMTPServer
Bases: XAObject
Represents an SMTP server configuration.
Properties
| Property | Type | Description |
|---|---|---|
name | str | Server name |
account_type | AccountType | Account type |
server_name | str | Host name |
port | int | Connection port |
user_name | str | Username |
password | None | Password (write-only) |
uses_ssl | bool | SSL enabled |
enabled | bool | Whether enabled |
authentication | AuthenticationMethod | Auth scheme |
---
XAMailMessageViewer
Bases: XAObject
Represents the main message viewer window.
Properties
| Property | Type | Description |
|---|---|---|
id | int | Unique identifier |
window | XAMailWindow | The window object |
mailbox_list_visible | bool | Mailbox list shown |
preview_pane_is_visible | bool | Preview pane shown |
sort_column | ViewerColumn | Sort column |
sort_ascending | bool | Sort direction |
visible_columns | list[str] | Visible columns |
Mailbox Properties
| Property | Type | Description |
|---|---|---|
inbox | XAMailbox | Top-level inbox |
drafts_mailbox | XAMailbox | Top-level drafts |
sent_mailbox | XAMailbox | Top-level sent |
trash_mailbox | XAMailbox | Top-level trash |
junk_mailbox | XAMailbox | Top-level junk |
outbox | XAMailbox | Top-level outbox |
Selection Properties
| Property | Type | Description |
|---|---|---|
selected_mailboxes | XAMailboxList | Selected mailboxes |
selected_messages | XAMailMessageList | Selected messages |
visible_messages | XAMailMessageList | Displayed messages |
Methods
messages(filter=None) → XAMailMessageList
Returns messages matching the filter.
---
Account Type Classes
XAMailIMAPAccount
Bases: XAMailAccount
IMAP-specific account properties.
| Property | Type | Description |
|---|---|---|
message_caching | CachingPolicy | Caching policy |
store_drafts_on_server | bool | Store drafts on server |
store_sent_messages_on_server | bool | Store sent on server |
store_junk_mail_on_server | bool | Store junk on server |
store_deleted_messages_on_server | bool | Store deleted on server |
compact_mailboxes_when_closing | bool | Auto-compact on close |
XAMailPOPAccount
Bases: XAMailAccount
POP-specific account properties.
| Property | Type | Description |
|---|---|---|
delete_mail_on_server | bool | Delete after download |
delete_messages_when_moved_from_inbox | bool | Delete on move |
delayed_message_deletion_interval | int | Days before server deletion |
big_message_warning_size | int | Size threshold for warning (-1 = no warning) |
XAMailICloudAccount
Bases: XAMailAccount
iCloud account (uses base account properties).
---
List Classes
All list classes support fast enumeration and bulk property access.
Common List Methods
# Bulk property access
messages = mailbox.messages()
subjects = messages.subject() # → list[str]
senders = messages.sender() # → list[str]
dates = messages.date_received() # → list[datetime]
# Filtering
unread = messages.by_read_status(False)
flagged = messages.by_flagged_status(True)
from_sender = messages.by_sender("user@example.com")XAMailMessageList
| Method | Returns |
|---|---|
subject() | list[str] |
sender() | list[str] |
content() | list[str] |
date_sent() | list[datetime] |
date_received() | list[datetime] |
read_status() | list[bool] |
flagged_status() | list[bool] |
junk_mail_status() | list[bool] |
message_size() | list[int] |
mailbox() | XAMailboxList |
XAMailboxList
| Method | Returns |
|---|---|
name() | list[str] |
unread_count() | list[int] |
account() | XAMailAccountList |
messages() | Combined messages |
XAMailAccountList
| Method | Returns |
|---|---|
name() | list[str] |
email_addresses() | list[list[str]] |
enabled() | list[bool] |
server_name() | list[str] |
mailboxes() | Combined mailboxes |
---
Enumerations
AccountType
| Value | Description |
|---|---|
IMAP | IMAP account |
POP | POP account |
SMTP | SMTP server |
ICLOUD | iCloud account |
UNKNOWN | Unknown type |
AuthenticationMethod
| Value | Description |
|---|---|
PASSWORD | Clear text password |
APOP | APOP |
KERBEROS5 | Kerberos V5 (GSSAPI) |
NTLM | NTLM |
MD5 | CRAM-MD5 |
EXTERNAL | TLS client certificate |
APPLE_TOKEN | Apple token |
NONE | No authentication |
Format
| Value | Description |
|---|---|
PLAIN_MESSAGE | Plain text |
RICH_MESSAGE | Rich text/HTML |
NATIVE | Native format |
HighlightColor
| Value | Description |
|---|---|
BLUE | Blue |
GRAY | Gray |
GREEN | Green |
NONE | No color |
ORANGE | Orange |
OTHER | Other color |
PURPLE | Purple |
RED | Red |
YELLOW | Yellow |
QuotingColor
| Value | Description |
|---|---|
BLUE | Blue |
GREEN | Green |
ORANGE | Orange |
OTHER | Other |
PURPLE | Purple |
RED | Red |
YELLOW | Yellow |
RuleType
| Value | Description |
|---|---|
FROM_HEADER | From header |
TO_HEADER | To header |
CC_HEADER | Cc header |
TO_OR_CC_HEADER | To or Cc header |
SUBJECT_HEADER | Subject header |
HEADER_KEY | Arbitrary header key |
ANY_RECIPIENT | Any recipient |
MESSAGE_CONTENT | Message content |
ACCOUNT | Account |
ATTACHMENT_TYPE | Attachment type |
MESSAGE_IS_JUNK_MAIL | Is junk mail |
SENDER_IS_IN_MY_CONTACTS | Sender in contacts |
SENDER_IS_NOT_IN_MY_CONTACTS | Sender not in contacts |
SENDER_IS_IN_MY_PREVIOUS_RECIPIENTS | Sender in previous recipients |
SENDER_IS_NOT_IN_MY_PREVIOUS_RECIPIENTS | Sender not in previous recipients |
SENDER_IS_MEMBER_OF_GROUP | Sender in group |
SENDER_IS_NOT_MEMBER_OF_GROUP | Sender not in group |
SENDER_IS_VIP | Sender is VIP |
MATCHES_EVERY_MESSAGE | Every message |
RuleQualifier
| Value | Description |
|---|---|
BEGINS_WITH_VALUE | Begins with |
ENDS_WITH_VALUE | Ends with |
DOES_CONTAIN_VALUE | Contains |
DOES_NOT_CONTAIN_VALUE | Does not contain |
EQUAL_TO_VALUE | Equals |
GREATER_THAN_VALUE | Greater than |
LESS_THAN_VALUE | Less than |
NONE | No qualifier |
ViewerColumn
| Value | Description |
|---|---|
ATTACHMENTS | Attachment count |
DATE_RECEIVED | Date received |
DATE_SENT | Date sent |
DATE_LAST_SAVED | Draft save date |
FLAGS | Message flags |
FROM | Sender name |
MAILBOX | Mailbox name |
MESSAGE_COLOR | Sort by color |
MESSAGE_STATUS | Read/replied status |
NUMBER | Message number |
RECIPIENTS | Recipients |
SIZE | Message size |
SUBJECT | Subject |
CachingPolicy
| Value | Description |
|---|---|
ALL_MESSAGES_AND_THEIR_ATTACHMENTS | Cache all |
ALL_MESSAGES_BUT_OMIT_ATTACHMENTS | Cache without attachments |
DO_NOT_KEEP_COPIES_OF_ANY_MESSAGES | Deprecated (maps to omit attachments) |
ONLY_MESSAGES_I_HAVE_READ | Deprecated (maps to omit attachments) |
---
Quick Reference Tables
Common Operations
| Task | Code |
|---|---|
| Get Mail app | mail = PyXA.Application("Mail") |
| Get inbox | inbox = mail.inbox |
| Get all messages | messages = inbox.messages() |
| Get unread messages | messages.by_read_status(False) |
| Get flagged messages | messages.by_flagged_status(True) |
| Open a message | message.open() |
| Create new message | msg = mail.outgoing_messages()[0] |
| Send message | msg.send() |
| Check for mail | mail.check_for_new_mail(account) |
Message Composition
import PyXA
mail = PyXA.Application("Mail")
# Create outgoing message
# (Use Mail's make command or reply/forward)
msg = message.reply(open_window=True)
msg.subject = "Re: " + message.subject
msg.content = "Thank you for your message..."
msg.send()Filtering Examples
# Get messages from specific sender
from_john = messages.by_sender("john@example.com")
# Get unread messages
unread = messages.by_read_status(False)
# Get messages by subject
important = messages.by_subject("Important")
# Bulk access
all_subjects = messages.subject() # Returns list[str]
all_senders = messages.sender() # Returns list[str]---
See Also
- PyXA Mail Documentation - Official PyXA documentation
- mail-basics.md - Getting started with Mail automation
- mail-recipes.md - Common automation patterns
- mail-advanced.md - Advanced techniques
- mail-rules.md - Rule automation
Mail JXA recipes
Filter unread flagged
JXA:
const msgs = Mail.inbox.messages.whose({ flaggedStatus: true, readStatus: false });PyXA:
import PyXA
mail = PyXA.Application("Mail")
inbox = mail.inboxes()[0]
# Filter unread flagged messages
unread_flagged = inbox.messages().filter(
lambda msg: msg.flagged and not msg.read_status
)
print(f"Found {len(unread_flagged)} unread flagged messages")Move messages (batch)
JXA:
const archive = Mail.accounts.byName("iCloud").mailboxes.byName("Archive");
Mail.move(msgs, { to: archive });PyXA:
import PyXA
mail = PyXA.Application("Mail")
# Get archive mailbox
icloud_account = mail.accounts().by_name("iCloud")
archive_mailbox = icloud_account.mailboxes().by_name("Archive")
# Move messages to archive
# Note: PyXA may require individual moves for reliability
for message in unread_flagged:
message.move_to(archive_mailbox)
print(f"Moved {len(unread_flagged)} messages to archive")Compose message
JXA:
const msg = Mail.OutgoingMessage({ subject: "Report", content: "See attached", visible: true });
Mail.outgoingMessages.push(msg);
msg.toRecipients.push(Mail.Recipient({ address: "client@example.com" }));PyXA:
import PyXA
mail = PyXA.Application("Mail")
# Create outgoing message
message = mail.outgoing_messages().push({
"subject": "Report",
"content": "See attached",
"to_recipients": ["client@example.com"],
"visible": True
})
print("Message composed and ready for sending")Attach file
JXA:
msg.content.attachments.push(Mail.Attachment({ fileName: Path("/Users/you/report.pdf") }));PyXA:
import PyXA
# Attach file to the message we just created
attachment_path = "/Users/you/report.pdf"
message.attachments().push({
"file_name": attachment_path
})
print(f"Attached file: {attachment_path}")Mail rule scripts (performMailActionWithMessages)
Handler skeleton
function performMailActionWithMessages(messages, rule) {
const Mail = Application("Mail");
for (let i = 0; i < messages.length; i++) {
const msg = messages[i];
const subject = msg.subject();
if (subject.includes("Urgent")) {
msg.flaggedStatus = true;
}
}
}Notes:
- Rule scripts must live in
~/Library/Application Scripts/com.apple.mail. - Keep work fast; long tasks will freeze Mail.
Using transcripts for follow-ups (Voice Memos)
- Meeting workflows can pass transcript text into the follow-up drafting step.
- Pattern: store transcript text in a temp file or variable, parse for agenda/decisions/action items, then:
- Inject bullets into the email body.
- Emit follow-up reminders (delegate to
automating-reminders). - Keep parsing lightweight inside Mail rule scripts; offload heavy parsing to an external helper invoked via
doShellScriptif needed.
Mailbox archiver
Archive messages older than N days
const Mail = Application("Mail");
const account = Mail.accounts.byName("iCloud");
const archive = account.mailboxes.byName("Archive");
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 30);
account.mailboxes().forEach(box => {
try {
const msgs = box.messages();
msgs.forEach(m => {
const received = m.dateReceived();
if (received && received < cutoff) {
Mail.move(m, { to: archive });
}
});
} catch (e) {}
});Notes:
- Batch move lists when possible for performance.
- Skip special mailboxes (Trash, Junk) as needed.
#!/usr/bin/env python3
"""
Create Email Script - PyXA Implementation
Creates and composes a new email message using Mail.app
Usage: python create_email.py "subject" "recipient@example.com" "body text"
"""
import sys
import PyXA
def create_email(subject, recipient, body):
"""Create and compose a new email message"""
try:
mail = PyXA.Application("Mail")
# Create outgoing message
message = mail.outgoing_messages().push({
"subject": subject,
"content": body
})
# Add recipient
message.to_recipients = [recipient]
# Make message visible for editing
message.visible = True
print(f"Email created with subject: {subject}")
return True
except Exception as e:
print(f"Error creating email: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 4:
print("Usage: python create_email.py 'subject' 'recipient@example.com' 'body text'")
sys.exit(1)
subject = sys.argv[1]
recipient = sys.argv[2]
body = sys.argv[3]
success = create_email(subject, recipient, body)
sys.exit(0 if success else 1)#!/usr/bin/env python3
"""
Create Mail Rule Script - PyXA Implementation
Creates a mail rule to automatically organize incoming emails
Usage: python create_mail_rule.py "Rule Name" "sender@domain.com" "target mailbox"
"""
import sys
import PyXA
def create_mail_rule(rule_name, sender_condition, target_mailbox):
"""Create a mail rule for automatic organization"""
try:
mail = PyXA.Application("Mail")
# Note: Mail rules are not directly scriptable via PyXA
# This script provides a template for manual rule creation
# and demonstrates how to set up the conditions
print(f"Creating mail rule: {rule_name}")
print(f"Condition: From contains '{sender_condition}'")
print(f"Action: Move to mailbox '{target_mailbox}'")
print("\nTo create this rule manually in Mail.app:")
print("1. Mail > Preferences > Rules")
print("2. Click '+' to add a new rule")
print(f"3. Description: {rule_name}")
print("4. If ANY of the following conditions are met:")
print(f" • From contains: {sender_condition}")
print("5. Perform the following actions:")
print(f" • Move Message to mailbox: {target_mailbox}")
print("6. Click OK to save")
# In a real implementation, you might use AppleScript
# or UI scripting to automate rule creation
applescript_rule = f'''
tell application "Mail"
make new rule with properties {{
name: "{rule_name}",
sender contains: "{sender_condition}",
move message: mailbox "{target_mailbox}" of account 1
}}
end tell
'''
print("\nEquivalent AppleScript:")
print(applescript_rule)
return True
except Exception as e:
print(f"Error creating mail rule template: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 4:
print("Usage: python create_mail_rule.py 'Rule Name' 'sender@domain.com' 'target mailbox'")
sys.exit(1)
rule_name = sys.argv[1]
sender = sys.argv[2]
mailbox = sys.argv[3]
success = create_mail_rule(rule_name, sender, mailbox)
sys.exit(0 if success else 1)#!/usr/bin/env python3
"""
Extract Email Addresses to Contacts Script - PyXA Implementation
Extracts email addresses from selected Mail messages and adds them to Contacts
Usage: python extract_emails_to_contacts.py
"""
import PyXA
import re
def extract_email_addresses(text):
"""Extract email addresses from text using regex"""
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
return re.findall(email_pattern, text)
def add_to_contacts(email, name=None):
"""Add email address to Contacts app"""
try:
contacts = PyXA.Application("Contacts")
# Check if contact already exists
existing_contacts = contacts.contacts().filter(lambda c: email in str(c.email_addresses or []))
if existing_contacts:
print(f"Contact with email {email} already exists")
return False
# Create new contact
contact_name = name or email.split('@')[0].replace('.', ' ').title()
new_contact = contacts.contacts().push({
"name": contact_name,
"email_addresses": [email]
})
print(f"Added contact: {contact_name} ({email})")
return True
except Exception as e:
print(f"Error adding contact: {e}")
return False
def main():
"""Main function to extract emails from selected messages"""
try:
mail = PyXA.Application("Mail")
# Get selected messages
selected_messages = mail.selection()
if not selected_messages:
print("No messages selected. Please select one or more messages in Mail.app")
return False
total_added = 0
for message in selected_messages:
# Extract emails from various fields
all_text = f"{message.sender or ''} {message.subject or ''} {message.content or ''}"
emails = extract_email_addresses(all_text)
for email in emails:
# Skip the sender's own email
if message.sender and email in message.sender:
continue
if add_to_contacts(email):
total_added += 1
print(f"Successfully added {total_added} contacts from {len(selected_messages)} messages")
return True
except Exception as e:
print(f"Error processing messages: {e}")
return False
if __name__ == "__main__":
success = main()
exit(0 if success else 1)#!/usr/bin/env python3
"""
Mail Search and Archive Script - PyXA Implementation
Searches for emails matching criteria and moves them to archive
Usage: python search_and_archive.py "search term" "archive mailbox name"
"""
import sys
import PyXA
def search_and_archive(search_term, archive_mailbox="Archive"):
"""Search for emails and move them to archive"""
try:
mail = PyXA.Application("Mail")
# Find all accounts
accounts = mail.accounts()
archived_count = 0
for account in accounts:
print(f"Searching in account: {account.name}")
# Get all mailboxes for this account
mailboxes = account.mailboxes()
# Find archive mailbox
archive_box = None
for mailbox in mailboxes:
if archive_mailbox.lower() in mailbox.name.lower():
archive_box = mailbox
break
if not archive_box:
print(f"Archive mailbox '{archive_mailbox}' not found in account {account.name}")
continue
# Search through all mailboxes for messages
for mailbox in mailboxes:
try:
messages = mailbox.messages()
# Filter messages containing search term
matching_messages = []
for msg in messages:
if (search_term.lower() in (msg.subject or "").lower() or
search_term.lower() in (msg.content or "").lower()):
matching_messages.append(msg)
# Move matching messages to archive
for msg in matching_messages:
msg.move_to(archive_box)
archived_count += 1
print(f"Archived: {msg.subject}")
except Exception as e:
print(f"Error processing mailbox {mailbox.name}: {e}")
continue
print(f"Total messages archived: {archived_count}")
return True
except Exception as e:
print(f"Error in search and archive: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python search_and_archive.py 'search term' ['archive mailbox name']")
sys.exit(1)
search_term = sys.argv[1]
archive_name = sys.argv[2] if len(sys.argv) > 2 else "Archive"
success = search_and_archive(search_term, archive_name)
sys.exit(0 if success else 1)#!/usr/bin/env python3
"""Trigger Mail Automation prompt via a read-only AppleScript call."""
import subprocess
import sys
from textwrap import dedent
APPLESCRIPT = dedent(
"""
tell application "Mail"
activate
set accountNames to name of every account
set inboxNames to name of every mailbox of inbox
return "Accounts: " & (accountNames as text) & " | Inbox mailboxes: " & (inboxNames as text)
end tell
"""
)
def main() -> int:
print("Requesting Automation permission for Mail...")
result = subprocess.run(
["osascript", "-e", APPLESCRIPT],
capture_output=True,
text=True,
)
if result.stdout.strip():
print(result.stdout.strip())
if result.returncode != 0:
print(result.stderr.strip() or "Mail check failed without error output.")
return result.returncode
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env bash
# Trigger Mail Automation prompt via a read-only AppleScript call.
set -euo pipefail
echo "Requesting Automation permission for Mail..."
osascript -e 'tell application "Mail"
activate
set accountNames to name of every account
set inboxNames to name of every mailbox of inbox
return "Accounts: " & (accountNames as text) & " | Inbox mailboxes: " & (inboxNames as text)
end tell'
echo "Mail responded. If prompted, grant Terminal/Python permission."