MacOS – Automated Deletion of Messages From OS X Mail

applescriptautomatoremailmacosmail.app

Okay, so I know this is a tricky issue as OS X's Mail seemingly lacks the ability to automatically delete or archive old messages, even though it has the necessary date-based rules to do so.

As such, I'm currently using a smart folder to find all old messages (except in mailboxes I prefer to keep) so that I can periodically delete them by opening the smart folder, selecting everything and hitting delete.

This is a bit more convenient, but I'd still prefer the process to be automatic. Is there any way that I could have Mail, an Automator script, Applescript or something similar find old messages with some criteria (or use my smart mailbox) and delete them, so that I can schedule the process to run automatically?

Best Answer

Okay, so I've found a solution using AppleScript, it's a fairly big script but it's not actually all that complicated; it simply loops through the mailboxes of every count, skipping any specified in a list, and looks for read, undeleted messages older than the specified number of days.

 on run arguments
    # Don't run if Mail isn't open
    if application "Mail" is not running then return

    set defaultExpiryDays to 45
    set dryRun to true

    # Standard deleted message mailboxes, used to relocate mail when "deleted"
    set trashMailboxes to {"bin", "trash", "deleted messages", "deleted items"}

    # These mailboxes will be ignored (messages won't be processed)
    set ignoreTheseMailboxes to trashMailboxes & {"all mail", "archive", "archived", "drafts", "junk", "junk e-mail", "sent", "sent items", "sent messages", "spam"}

    set numberOfArguments to number of items in arguments
    if numberOfArguments is greater than 0 then
        set expiryDate to item 1 of arguments
        if number of arguments is greater than 1 then
            set ignoreTheseMailboxes to ignoreTheseMailboxes & rest of arguments
        end if
    else
        set expiryDate to defaultExpiryDays
    end if
    set expiryDate to (current date) - (expiryDate * days)

    set countdown to 10
    repeat while countdown is greater than 0
        try
            return processMail(expiryDate, ignoreTheseMailboxes, trashMailboxes, dryRun)
        on error number -1712
            set countdown to countdown - 1
        end try
    end repeat
    return "Communication with Mail timed out"
end run

on processMail(expiryDate, ignoreTheseMailboxes, trashMailboxes, dryRun)
    set messagesDeleted to 0
    set messagesMoved to 0

    set results to {}
    set newline to "
"

    tell application "Mail"
        set theAccounts to every account

        repeat with eachAccount in theAccounts
            set accountName to name of eachAccount
            set accountNameWritten to false
            set accountTrashMailbox to false

            set theMailboxes to every mailbox of eachAccount
            repeat with eachMailbox in theMailboxes
                set mailboxName to name of eachMailbox
                set mailboxNameWritten to false

                if ignoreTheseMailboxes does not contain mailboxName then
                    set theMessages to (every message of eachMailbox whose (deleted status is false) and (read status is true) and (date received is less than or equal to expiryDate))
                    set mailboxResults to {}
                    repeat with eachMessage in theMessages
                        try
                            if accountNameWritten is false then
                                set the end of mailboxResults to accountName
                                set accountNameWritten to true
                            end if
                            if mailboxNameWritten is false then
                                set the end of mailboxResults to "  " & mailboxName
                                set mailboxNameWritten to true
                            end if

                            # Find this account's trash mailbox (if we haven't already)
                            if accountTrashMailbox is false then
                                repeat with mailboxName in trashMailboxes
                                    set foundMailboxes to (every mailbox in eachAccount whose name is mailboxName)
                                    if number of items in foundMailboxes is greater than 0 then
                                        set accountTrashMailbox to first item of foundMailboxes
                                        exit repeat
                                    end if
                                end repeat
                                if accountTrashMailbox is false then set accountTrashMailbox to missing value
                            end if

                            if accountTrashMailbox is not missing value then
                                set the end of mailboxResults to "      Moved:  " & (subject of eachMessage)
                                if not dryRun then move eachMessage to accountTrashMailbox
                                set messagesMoved to messagesMoved + 1
                            else
                                set the end of mailboxResults to "      Deleted:    " & (subject of eachMessage)
                                if not dryRun then delete eachMessage
                                set messagesDeleted to messagesDeleted + 1
                            end if
                        end try
                    end repeat

                    if number of items in mailboxResults is greater than 0 then
                        set AppleScript's text item delimiters to newline
                        set end of results to mailboxResults as rich text
                    end if
                end if
            end repeat
        end repeat
    end tell

    set messagesMatches to messagesDeleted + messagesMoved
    if messagesMatches is greater than 0 then
        set statistics to {}
        set AppleScript's text item delimiters to ""
        if messagesDeleted is greater than 0 then
            set the end of statistics to (messagesDeleted & " message(s) deleted") as text
        end if
        if messagesMoved is greater than 0 then
            set the end of statistics to (messagesMoved & " message(s) moved") as text
        end if

        set AppleScript's text item delimiters to ", "
        set the end of results to (statistics as text)
    else
        set the end of results to "No messages were deleted."
    end if

    set AppleScript's text item delimiters to newline
    return results as text
end processMail

The script deletes messages by locating an account's trash folder (if it has one) and moving them, which is a very slow process if it has a lot of e-mails to move initially, however it prevents the need to rescan. It will only use the regular delete option if it can't determine the trash folder (i.e - didn't find one from the names in trashMailboxes), while this is faster it may result in messages being reprocessed if the script is run frequently (e.g - daily).

To configure the script's setting you can change defaultExpiryDays to the number of days of e-mail to keep, everything older and read is deleted. The dryRun setting is set initially to true, which means the script will report matches but won't actually delete or move anything, once you're happy the script is matching the e-mails you expect it to then you can set this false.

The trashMailboxes lists the mailbox names that the script will move e-mail to if found for an account. The ignoreTheseMailboxes list contains mailboxes that shouldn't be processed, and covers most common ones, including folders that Mail's normal mailbox behaviours should cover already.

The script can be run by anything that can trigger an AppleScript, such as iCal, in which case it will use its defaults. It can also be triggered via a shell script or launchd using a command such as osascript /path/to/script.scpt, which will likewise use the defaults, or you can specify additional arguments in which case the first is the number of days to keep, and any further arguments are additional mailboxes to skip, for example osascript /path/to/script.scpt 14 foo bar will keep the last two weeks worth of read e-mails, and will skip any mailboxes named "foo" or "bar" in addition to the defaults.

If you want to automate the process with launchd then you can create a file under ~/Library/LaunchAgents with the extension .plist, and contents such as:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.example.Mail.Clean</string>
    <key>ProgramArguments</key>
    <array>
        <string>osascript</string>
        <string>~/path/to/script.scpt</string>
        <string>45</string>
    </array>
    <key>EnableGlobbing</key>
    <true/>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Minute</key>
        <integer>15</integer>
        <key>Hour</key>
        <integer>13</integer>
        <key>Weekday</key>
        <integer>0</integer>
    </dict>
</dict>
</plist>

Which will keep 45 days worth of e-mails, and will run every Sunday at 1:15pm (remember to set the path to your script under program arguments!).

Once saved the launch agent will then load either next time your shutdown/restart, or you can run launchctl load ~/Library/LaunchAgents/agent.plist with the correct name for your plist file.