Repository navigation
Releases: 0xSMW/mail.app-cli
Release list
v3.2.0: Optional email sending restriction
Mail.app CLI 3.2.0 adds an optional sending restriction for workflows where you want to read mail and prepare drafts without sending messages.
Disable sending
Save the restriction in your configuration:
mail-app-cli config set send.enabled falseOr disable sending for one invocation with the global flag:
mail-app-cli --no-send tuiThe restriction covers send, drafts send, replies, reply-all, forwards, and sending from the TUI. Blocked CLI sends exit with status 1 and explain that email sending is disabled. The TUI keeps your composed text available to save as a draft.
Draft creation and management, reading mail, and dry-run previews remain available. Sending stays enabled by default. --no-send=false cannot override a stored restriction.
Use mail-app-cli config show to inspect the effective setting and its source. Restore the default with mail-app-cli config unset send.enabled, or explicitly enable sending with mail-app-cli config set send.enabled true.
Install or update
CGO_ENABLED=1 go install github.com/0xSMW/mail.app-cli/v3/cmd/mail-app-cli@v3.2.0Requires macOS, Go 1.25 or newer, and Xcode Command Line Tools. Keep Automation and Full Disk Access enabled for the app running the CLI.
v3.1.0: Find email links and unsubscribe opportunities
Mail.app CLI 3.1.0 makes the destinations behind email links available directly in your terminal. Find the real URL behind “unsubscribe here,” inspect one message, or discover unsubscribe opportunities across whole folders.
Find unsubscribe opportunities
- Use
unsubscribe show MESSAGE_IDto inspect local mailing-list headers, HTML footer links, and plain-text unsubscribe or preference controls. - Use
unsubscribe scan MAILBOX...to traverse entire folders, or add--all-accountsto include every enabled account. - Review results grouped by mailing-list identity, with provisional fallback groups clearly labeled. Exact destinations retain their source and message evidence, including controls found only in older messages.
- Resume an interrupted or time-limited scan from its private checkpoint. Retry incomplete local sources when they become available, or restart for fresh observations.
- See explicit coverage, pending work, and extraction exceptions. Recovered links remain available when a local source is partial or missing.
Access any email link
links MESSAGE_ID exposes labels and full destinations. Use show --links for links beside the message body, or consume structured link inventories from show and messages read. Personalized URL parameters and MIME source information are retained.
Discovery is read-only: it never opens or fetches destinations, sends mail, or changes subscriptions. Unsubscribe discovery inspects all supported local body alternatives without asking Mail to render or download missing content.
mail-app-cli unsubscribe show 100001 --json
mail-app-cli unsubscribe scan INBOX --all-accounts --json
mail-app-cli links 100001 --jsonInstall or update
CGO_ENABLED=1 go install github.com/0xSMW/mail.app-cli/v3/cmd/mail-app-cli@v3.1.0Requires macOS, Go 1.25 or newer, and Xcode Command Line Tools. Keep Automation and Full Disk Access enabled for the app running the CLI.
v3.0.0: Native transport, Exchange support, and background Gmail Archive
Mail.app CLI 3.0 sends typed Apple Events to Mail.app and reads Mail's local data through embedded SQLite. This reduces subprocess overhead and Mail rendering stalls. Exchange search, moves, and drafts are more reliable. Gmail Inbox Archive runs in a background viewer, so you can keep working while an agent archives. Connected Gmail accounts can manage labels. Existing commands and JSON schema version 1 remain compatible.
Native transport
- Native is the default on macOS builds with cgo. Apple Events handle metadata, message changes, composition, rules, and signatures. Read-only embedded SQLite handles local lists and searches.
- Bodies, headers, and attachments come from Mail's cached MIME when the cached copy is complete. Missing local content falls back to scoped Mail reads where supported.
--backend legacyorMAIL_APP_CLI_BACKEND=legacyselects the scripting backend, which non-cgo builds use automatically. Failed native changes never retry through legacy.
Exchange and search
- Exchange mailboxes are recognized in Inbox and other folders, so search and cached content include previously missing messages.
- Scoped reads recover from a lagging local index and confirm the requested account and mailbox.
- Incomplete searches fail unless you request partial results.
- Moves and archives stay verifiable when sync changes a local ID or size. Matching uses RFC message identity, account, sender, subject, and sent time. Ambiguous matches report as unverified.
Drafts and sending
- On the native backend, compose, reply, reply all, and forward use Apple Events in the CLI and TUI and keep Mail's original message relationships.
sendanddrafts createaccept--reply-to,--reply-all, and--forward-of. - You can create, update, and send saved plain-text drafts with ordinary attachments. Recipients, blank paragraphs, Unicode filenames, and file contents are preserved.
- Both backends record the draft before sending. After Mail acknowledges submission, they delete the draft only if it is unchanged. Uncertain sends are never resubmitted automatically. Acknowledgment does not confirm delivery, and Mail may normalize newlines in text attachments.
- Drafts with custom formatting, inline content, signing, encryption, or reply headers stay in Mail.
Gmail
- In 2.4, Gmail Inbox Archive opened a separate window. It now uses a background viewer with the preview hidden, on both backends. It never activates Mail, raises windows, or changes focus, and it closes only its own viewer. It stops before pressing if the message or button is missing or ambiguous, and it never repeats an uncertain archive.
- New label commands connect through OAuth and store tokens in Keychain. They list labels and add or remove Important and existing custom labels, with dry runs, receipts, and verification against Gmail. A mismatched identity stops the change. Labels apply to Gmail only.
Upgrade and permissions
- The module path is now
github.com/0xSMW/mail.app-cli/v3, and the install script installs/v3. - Native source builds need macOS, Go 1.25 or newer, and Xcode Command Line Tools.
- Grant Automation access to Mail and Full Disk Access to the app running the CLI. Gmail Inbox Archive also needs Accessibility access.
mail-app-cli doctorreports access and the active backend.
Install
CGO_ENABLED=1 go install github.com/0xSMW/mail.app-cli/v3/cmd/mail-app-cli@v3.0.02.4.0 — Sender, drafts, and mailbox reliability
Mail.app CLI 2.4 improves sender selection, draft creation and sending, and repeatable mailbox cleanup.
Sending and drafts
- Select a configured sender address with
--from. Sending and draft creation check the account and sender before submitting or saving, preventing fallback to another account. - Create and send messages without the attachment-access error. File attachments use Mail’s native attachment operation, including Unicode filenames.
- Find Drafts through Mail’s account-owned special mailbox references, including localized and nested folders.
- Send supported plain-text drafts containing Mail’s standard compose headers. Rich-text, inline, threaded, signed, and encrypted drafts still require sending through Mail.
- Verify saved drafts directly through Mail instead of waiting for the Envelope Index. Send receipts distinguish submission from pending original-draft cleanup; do not resend a message already marked
submitted: true.
Mailbox cleanup and recovery
- Gmail Inbox archives use Mail’s native Archive control. Grant Accessibility access to the application running the CLI; the operation briefly opens a separate Mail window and stops if it cannot confirm the intended message.
- Verify final source, destination, and requested read state with bounded observation using
--settleand--verify-budget. - Recheck saved archive/move receipts with
messages reconcile receipt.jsonwithout repeating the mutation. - Use
--mark-read-on-skipwith--mark-readto explicitly mark an already-archived record read. - Synchronization requests target the selected account. Local counts and receipts describe Mail’s observed state, rather than guaranteeing server-side labels.
Reads and scans
- Body extraction reports diagnostics for images, nontext parts, partial files, and MIME alternative selection. Request alternative text with
--body-alternatives. - Save and resume cumulative mailbox traversal with
messages scan --checkpoint /absolute/path/history.json; use--restart-checkpointfor a fresh scan.
Install
Requires macOS and Go 1.25 or newer:
go install github.com/0xSMW/mail.app-cli/v2/cmd/mail-app-cli@v2.4.02.3.0: Body reads from disk and paged scans
mail-app-cli 2.3 reads message bodies from the files Mail.app keeps on disk, so read and show no longer wait for Mail.app to render a body. A message that used to stall Mail.app no longer holds up the commands queued behind it. messages scan can now page through a mailbox of any size with a cursor, and verified archives and moves report what they found in the source and the destination. A few outputs change in ways a script can notice. They are listed under "Changes that can affect scripts".
Summary
messages readandshowtake the body from Mail.app's message file on disk and ask Mail.app only for metadata. Each result hascontentSource, which isdiskormail. A message with no readable file falls back to Mail.app, and--body-source mailmakes Mail.app render the body as before.messages readitems carrydiagnosticswith the time spent in each phase of the read. When a read times out,pendingnames the operation Mail.app had not answered andlastCompletednames the one before it.messages scantakes--cursorand returnsnextCursor. To read a mailbox to the end, repeat the same arguments with--cursor "$NEXT_CURSOR"until a result has nonextCursor. Pages run newest first, and each coverage entry reportsexhaustedandremaining. A cursor works only with the mailboxes and filters it was issued for, though--limitcan change between pages.- Verified archive and move receipts carry
observedwithsourcePresent,destinationPresent,destinationRead, and the message's current local IDs insourceIdsanddestinationIds. When Mail.app regenerates a Gmail message in INBOX after an archive,verifyStatusstayspresent_in_sourceandsourceIdsholds the new ID to act on. - Mailbox lookup reads each level of the mailbox tree from Mail.app in one request instead of one mailbox at a time. Every command that resolves a mailbox through Mail.app is faster, mutations included.
list --with-contentstill asks Mail.app to render each body, so it can still stall. Read bodies withmessages readorshow.
Changes that can affect scripts
- Body text read from disk can differ slightly from Mail.app's rendering. It uses the HTML part when there is one, drops elements styled
display:noneand>reply markers, keeps list markers, and leaves out the placeholder character Mail.app inserts for each inline image. It applies inline styles only, so spacing can differ where a stylesheet sets the layout. Pass--body-source mailto get Mail.app's text. message not foundnow means Mail.app answered and the mailbox does not hold the ID. A Mail.app error duringreadorshowused to be reported as not found. It now returns an error that names the phase where it happened.- A mailbox lookup that times out or loses its connection to Mail.app now reports that failure. It used to report the mailbox as missing.
messages scan --queryused to stop at 50 results per mailbox and still reportcomplete: true. Scan now applies--queryand--unreadbefore--limitand returns every match, one page at a time. A mailbox with more matches than--limitsetscomplete: false, exits 5, and returns anextCursorto continue.
For direct Go installation, use:
go install github.com/0xSMW/mail.app-cli/v2/cmd/mail-app-cli@latest2.2.1 — Draft attachments and installation fixes
- Attach files when creating or updating drafts with repeatable
--attachflags. - Preserve existing attachments when editing a draft’s subject or body.
- Wait for attachments to finish saving and retain the original draft if replacement verification fails.
- Fix the installer and
make installto produce the expectedmail-app-cliexecutable.
For direct Go installation, use:
go install github.com/0xSMW/mail.app-cli/v2/cmd/mail-app-cli@latest2.2.0 — Faster mail triage
mail-app-cli 2.2 makes daily mail triage faster, with multi-mailbox scans, selected-message reading, and quicker verified batches.
Summary
messages scanreads multiple mailboxes in one command, combines duplicate messages while retaining their mailbox membership, and reports incomplete coverage explicitly.messages readretrieves the bodies of selected messages together. Successful reads remain available if another message fails or times out, with per-message errors and configurable time limits.- Compatible batch actions reuse a serial Mail.app connection to reduce repeated startup overhead while preserving durable receipts and recovery information.
- Read and flag verification finishes as soon as the requested state is confirmed. Archive and delete retain their synchronization checks.
- Updated workflow guidance helps agents scan metadata first, read only relevant bodies, and combine marking messages read with verified cleanup actions.
2.1.3
Summary
- Verified mailbox actions stay responsive in very large Gmail mailboxes. An 18-message verified batch now completes in 7.18 seconds instead of 413 seconds—about 57× faster.
- Archive confirmation is more trustworthy: success means a message remains available in All Mail and no longer appears in INBOX after synchronization settles.
- Large batches can apply their requested actions without pausing for a long verification step after every message.
- Interrupted commands stop their Mail.app automation promptly, so later commands do not queue behind abandoned work.
- Durable receipts keep confirmed, uncertain, and incomplete outcomes distinct, making interrupted batches safer to recover.
2.1.2
Summary
- Archive and move verification now waits through brief Mail and iCloud resync delays, preventing successful moves from being reported as failed.
- Verification can find messages after Mail assigns them new local IDs, even when RFC Message-ID lookup temporarily fails.
- Mutation receipts now keep applied moves separate from inconclusive destination verification, so moved counts remain accurate.
- Timeout and cancellation outcomes remain clearly reported as
unknown_after_timeout.
2.1.1
Summary
- Every message mutation now creates a durable receipt before changing Mail, so completed work remains recoverable if a command is interrupted or its output is lost.
--report-filekeeps its existing JSON format and now includes a JSONL journal alongside it for per-step progress and recovery.- Archive and move verification now follows stable message identity when Mail assigns a new local ID, with clear confirmed, failed, and unknown outcomes.
- Gmail archive now handles messages carrying both INBOX and Important correctly, removing the INBOX label without stripping Important.
smart listnow reports Mail’s Today view from messages received since local midnight, including explicit total and unread counts.- Custom Smart Mailboxes that Mail does not expose through automation now return a clear unsupported result instead of a successful empty list.