Apple Mail - SaneBox
Summary: Automatically applies SpamSieve to unread messages in the @SaneLater and other mailboxes.
Requires: SpamSieve, Apple Mail, SaneBox
Install Location: ~/Library/Application Scripts/com.apple.mail or ~/Library/Scripts/Applications/Mail/
Last Modified: 2021-07-15
Description
The SaneBox service filters incoming messages into different mailboxes. This presents a problem because Apple Mail only knows how to apply its rules (such as SpamSieve) to new messages that arrive in the inbox. This script lets SpamSieve filter messages that arrive in other mailboxes such as @SaneLater.
Before using this script, make sure that the regular SpamSieve setup is accurately filtering the spam messages from your inbox. Mark as read any older messages in @Sane mailboxes that you do not want to be checked for spam.
Download this script and enter your account and mailbox names where it says return {{"Account 1", {"@SaneLater"}}}. You can get the AppleScript names using the Apple Mail - List Mailboxes script. For subfolders, use a slash, e.g. "Mailbox/Submailbox".
Go to Mail’s Preferences window and create a new rule at the top of the list (above the SpamSieve rule) called SaneBox SpamSieve. The conditions should say:
Every Message
The actions should say:
Run AppleScript […]Apple Mail - SaneBox.scpt
After choosing Run AppleScript from the pop-up menu, select the Apple Mail - SaneBox.scpt file (using either the pop-up menu or the Choose… button).
Now, whenever you receive a new message in the inbox, SpamSieve will look in all the mailboxes that you specified and check all the unread messages. Any that are spam will be moved to the Junk mailbox.
Normally, the script will run when Mail receives a new message in the inbox and applies the “SaneBox SpamSieve” rule. You can also set up the script as a standalone application. This has less overhead than running the script from within Mail, and it allows the script to run once every few minutes rather than for every message received. To do this, use Script Editor to save the script as an application (with the Stay Open option checked) and then launch the application. You can set in the “idle” handler how often it should run.
To troubleshoot this script, you can run it in Script Editor (a.k.a. AppleScript Editor) and look for any error messages in the Console application. You can also enable debug logging by changing pEnableDebugLogging from false to true.
Note: On macOS 10.8 or later, the script must be saved in the folder /Users/<username>/Library/Application Scripts/com.apple.mail. This is because Mail is sandboxed and only has access to run scripts in that folder. To access the Library folder, click on the Go menu in the Finder while holding down the Option key.
Installation Instructions · Download in Compiled Format · Download in Text Format
Script
property pMarkSpamMessagesRead : false
property pChangeJunkStatus : true
property pColorSpamMessages : true
property pFlagSpamMessages : false
property pSpamMailboxName : "Spam"
property pMoveBlueMessagesToTrash : false
property pMoveGrayMessagesToTrash : false
property pMovePurpleMessagesToTrash : false
property pMoveRedMessagesToTrash : false
property pMoveOrangeMessagesToTrash : false
property pMoveYellowMessagesToTrash : false
property pEnableDebugLogging : false
global useJunkMailbox
on accountAndMailboxNames()
    -- Enter your account name, followed by a list of mailboxes for that account that should be filtered.
    -- The account name comes from the "Description" field in the Accounts tab of Mail's preferences.
    -- For example, this would filter the @SaneLater mailbox of Account 1:
    -- return {{"Account 1", {"@SaneLater"}}}
    -- And this would filter the @SaneLater and Other mailboxes of the Personal account
    -- and the @SaneLater mailbox of the Work account:
    -- return {{"Personal", {"@SaneLater", "Other"}}, {"Work", {"@SaneLater"}}}
    return {{"Account 1", {"@SaneLater"}}}
end accountAndMailboxNames
on run
    -- This is executed when you run the script directly.
    my filterSaneMailboxes()
end run
on idle
    -- This is executed periodically when the script is run as a stay-open application.
    my filterSaneMailboxes()
    return 60 * 5 -- Run again in 5 minutes.
end idle
using terms from application "Mail"
    on perform mail action with messages _messages
        -- This is executed when Mail runs the rule.
        my filterSaneMailboxes()
    end perform mail action with messages
end using terms from
on filterSaneMailboxes()
    if application "Mail" is not running then return
    tell application "SpamSieve"
        set useJunkMailbox to lookup single key "AppleMailUseJunkMailbox" without default value
    end tell
    tell application "Mail"
        repeat with _pair in my accountAndMailboxNames()
            set {_accountName, _mailboxNames} to _pair
            try
                set _account to account _accountName
                repeat with _mailboxName in _mailboxNames
                    try
                        my filterAccountMailboxNamed(_account, _mailboxName)
                    on error _error
                        my logToConsole("Error checking mailbox “" & _mailboxName & "” from account “" & _accountName & "”: " & _error)
                    end try
                end repeat
            on error _error
                my logToConsole("Error checking account “" & _accountName & "”: " & _error)
            end try
        end repeat
    end tell
end filterSaneMailboxes
on filterAccountMailboxNamed(_account, _mailboxName)
    tell application "Mail"
        set _mailbox to _account's mailbox _mailboxName
        my debugLog(my makeLogMessage("Start checking mailbox", _mailbox, ""))
        set _total to count of messages of _mailbox
        my debugLog(my makeLogMessage("Total messages in mailbox", _mailbox, _total))
        my debugLog(my makeLogMessage("Getting unread, non-deleted messages in mailbox", _mailbox, ""))
        with timeout of 3 * 60 seconds
            set _messages to messages of _mailbox whose read status is false and deleted status is false
        end timeout
        my debugLog(my makeLogMessage("Messages to process in mailbox", _mailbox, count of _messages))
        
        set _spamMailbox to my spamMailboxForAccount(_account)
        repeat with _message in _messages
            my processMessageIfSpam(_message, _spamMailbox)
        end repeat
        my debugLog(my makeLogMessage("Finished checking mailbox", _mailbox, ""))
    end tell
end filterAccountMailboxNamed
on processMessageIfSpam(_message, _spamMailbox)
    set _source to my sourceFromMessage(_message)
    try
        tell application "SpamSieve"
            set _score to score message _source
        end tell
    on error _error number _errorNumber
        my logToConsole("Error " & _errorNumber & " (" & _error & ") scoring message source: " & _source)
        return false
    end try
    tell application "Mail"
        my debugLog("Spam score of message is " & _score & ": " & _message's subject)
        set _isSpam to _score ≥ 50
        if pChangeJunkStatus then
            set _message's junk mail status to _isSpam
        end if
        if _isSpam and pMarkSpamMessagesRead then
            set _message's read status to true
        end if
        set _moveToTrash to my colorMessageAndDecideIfShouldMoveToTrash(_message, _score)
        if _moveToTrash then
            delete _message
        else if _isSpam then
            my moveMessage(_message, _spamMailbox)
        end if
        return _isSpam
    end tell
end processMessageIfSpam
on colorMessageAndDecideIfShouldMoveToTrash(_message, _score)
    tell application "Mail"
        set _table to {¬
            {99, blue, pMoveBlueMessagesToTrash, 6}, ¬
            {95, gray, pMoveGrayMessagesToTrash, 5}, ¬
            {88, purple, pMovePurpleMessagesToTrash, 4}, ¬
            {81, red, pMoveRedMessagesToTrash, 3}, ¬
            {75, orange, pMoveOrangeMessagesToTrash, 2}, ¬
            {50, yellow, pMoveYellowMessagesToTrash, 1}, ¬
            {0, none, false, -1}}
        -- Flag colors chosen so that messages sort by spamminess: gray, purple, blue, green, yellow, orange, none
        repeat with _row in _table
            set {_threshold, _color, _moveToTrash, _flagColor} to _row
            if _score ≥ _threshold then
                if pColorSpamMessages then
                    set _message's background color to _color
                end if
                if pFlagSpamMessages then
                    set _message's flag index to _flagColor
                end if
                return _moveToTrash
            end if
        end repeat
    end tell
end colorMessageAndDecideIfShouldMoveToTrash
on spamMailboxForAccount(_account)
    tell application "Mail"
        try
            return mailbox pSpamMailboxName of _account
        on error
            return mailbox pSpamMailboxName
        end try
    end tell
end spamMailboxForAccount
-- Logging
on debugLog(_message)
    if pEnableDebugLogging then my logToConsole(_message)
end debugLog
on logToConsole(_message)
    set _logMessage to "SpamSieve [Apple Mail SaneBox] " & _message
    do shell script "/usr/bin/logger -s " & _logMessage's quoted form
end logToConsole
on makeLogMessage(_action, _mailbox, _detail)
    return _action & " " & my describeMailbox(_mailbox) & ": " & _detail
end makeLogMessage
on describeMailbox(_mailbox)
    tell application "Mail"
        set _mailboxName to _mailbox's name
        try
            set _accountName to name of _mailbox's account
        on error
            set _accountName to "On My Mac"
        end try
        return "“" & _accountName & "” / “" & _mailboxName & "”"
    end tell
end describeMailbox
-- Logging Helpers
on sourceFromMessage(_message)
    tell application "Mail"
        my debugLog(my makeLogMessage("Getting source of message in", _message's mailbox, _message's subject))
        return _message's source
    end tell
end sourceFromMessage
on moveMessage(_message, _mailbox)
    tell application "Mail"
        if useJunkMailbox then
            my debugLog("Moving message to Junk: " & _message's subject)
            move _message to junk mailbox
        else
            my debugLog(my makeLogMessage("Moving message to", _mailbox, _message's subject))
            set _message's mailbox to _mailbox
        end if
    end tell
end moveMessage