Apple Mail - Move If Spam

Summary: Of the selected messages, moves the spam ones to the Junk mailbox.
Requires: SpamSieve, Apple Mail
Install Location: ~/Library/Scripts/Applications/Mail or ~/Library/Application Scripts/com.apple.mail
Last Modified: 2021-07-15

Description

Normally, SpamSieve uses a plug-in and a special rule to move spam messages to the Junk mailbox. This script is an alternative way of using SpamSieve from Apple Mail that is useful in two main circumstances:

  1. Sometimes it is not possible to use SpamSieve’s Apple Mail plug-in because of a new OS version, an incompatibility with another plug-in, or because you are trying to troubleshoot a crash in Mail.

    To use the script in this way, open the Library folder and install the script in the ~/Library/Application Scripts/com.apple.mail folder. Then create a rule in Apple Mail called Move If Spam (do not put “SpamSieve” in the name of the rule) with conditions Every Message and actions Run AppleScript. Select the Apple Mail - Move If Spam.scpt file as the script.

  2. Normally, to re-apply SpamSieve to a bunch of selected messages, you would use the Message ‣ Apply Rules command. This script is useful when you want to re-apply SpamSieve without applying all the other rules.

    To use the script in this way, open the Library folder and install the script in the ~/Library/Scripts/Applications/Mail folder. You can invoke the script via the system’s script menu.

Disadvantages of this script compared to the SpamSieve rule are:

Installation Instructions · Download in Compiled Format · Download in Text Format

Script

property pMarkSpamMessagesRead : false
property pChangeJunkStatus : true
property pColorSpamMessages : true
property pFlagSpamMessages : false
property pMoveBlueMessagesToTrash : false
property pMoveGrayMessagesToTrash : false
property pMovePurpleMessagesToTrash : false
property pMoveRedMessagesToTrash : false
property pMoveOrangeMessagesToTrash : false
property pMoveYellowMessagesToTrash : false
property pMoveGoodMessagesToInbox : false
property pMarkGoodMessagesUnread : false
property pEnableDebugLogging : false
global useJunkMailbox

on run
    
tell application "Mail"
        
set _messages to the selection as list
        
my processMessages(_messages)
    
end tell
end run

using terms from application "Mail"
    
on perform mail action with messages _messages
        
-- This is executed when Mail runs the rule.
        
my processMessages(_messages)
    
end perform mail action with messages
end using terms from

on processMessages(_messages)
    
try
        
set {useJunkMailbox, _spamFolderName, _accountName} to my lookupDefaults({"AppleMailUseJunkMailbox", "AppleMailTrainSpamName", "AppleMailSpamAccountName"}, {false, "Spam", ""})
        
repeat with _message in _messages
            
my processMessage(_message, _spamFolderName, _accountName)
        
end repeat
    
on error _errorMessage
        
my logToConsole("Error processing message: " & _errorMessage)
    
end try
end processMessages

on processMessage(_message, _spamFolderName, _accountName)
    
tell application "Mail"
        
set _spamMailbox to my spamMailboxForMessage(_message, _spamFolderName, _accountName)
        
set _isSpam to my processMessageIfSpam(_message, _spamMailbox)
        
if not _isSpam then
            
my processGoodMessage(_message)
        
end if
    
end tell
end processMessage

on spamMailboxForMessage(_message, _spamFolderName, _accountName)
    
tell application "Mail"
        
if _accountName is "" then
            
return mailbox _spamFolderName
        
end if
        
try
            
set _account to account _accountName
            
try
                
-- Use per-account if possible
                
set _account to (account of mailbox of _message)
                
if _account is missing value then
                    
set _account to account _accountName
                
end if
            
end try
            
return mailbox _spamFolderName of _account
        
on error _error
            
if not (exists mailbox _spamFolderName) then
                
make new mailbox with properties {name:_spamFolderName}
            
end if
            
return mailbox _spamFolderName
        
end try
    
end tell
end spamMailboxForMessage

on processMessageIfSpam(_message, _spamMailbox)
    
set _source to my sourceFromMessage(_message)
    
tell application "SpamSieve"
        
set _score to score message _source
    
end tell
    
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 processGoodMessage(_message)
    
tell application "Mail"
        
try
            
set junk mail status of _message to false
        
end try
        
if pMarkGoodMessagesUnread then
            
try
                
set read status of _message to true
            
end try
        
end if
        
if pMoveGoodMessagesToInbox then
            
set _message's mailbox to my inboxForMessage(_message)
        
end if
    
end tell
end processGoodMessage

on inboxForMessage(_message)
    
tell application "Mail"
        
set _accounts to accounts where its enabled is true
        
set _account to my accountFromMessage(_message, _accounts)
        
set _mailbox to my inboxFromAccount(_account)
        
return _mailbox
    
end tell
end inboxForMessage

on accountFromMessage(_message, _accounts)
    
tell application "Mail"
        
try
            
set _recipientAddresses to my recipientAddressesFromMessage(_message)
            
repeat with _account in _accounts
                
set _accountAddresses to my addressesForAccount(_account)
                
repeat with _recipientAddress in _recipientAddresses
                    
ignoring case
                        
if _recipientAddress is in _accountAddresses then return _account
                    
end ignoring
                
end repeat
            
end repeat
        
end try
        
try
            
-- Only do this as a fallback, because some people have one remote Spam mailbox but multiple inboxes.
            
set _account to account of mailbox of _message
            
if _account is not missing value then return _account -- Can happen for local mailboxes.
        
end try
        
return item 1 of _accounts
    
end tell
end accountFromMessage

on inboxFromAccount(_account)
    
tell application "Mail"
        
set _names to {"INBOX", "Inbox", "innboks", "Posteingang", "Boite de reception"}
        
repeat with _name in _names
            
try
                
set _mailbox to mailbox _name of _account
                
return _mailbox
            
end try
        
end repeat
        
return inbox
    
end tell
end inboxFromAccount

on recipientAddressesFromMessage(_message)
    
tell application "Mail"
        
set _addresses to {}
        
set _addresses to _addresses & my toAddressesFromMessage(_message)
        
set _addresses to _addresses & my ccAddressesFromMessage(_message)
        
set _addresses to _addresses & my extraAddressesFromMessage(_message)
        
if _addresses is not {} then return _addresses
        
return my parseAddressesFromMessage(_message)
    
end tell
end recipientAddressesFromMessage

on toAddressesFromMessage(_message)
    
tell application "Mail"
        
set _addresses to {}
        
if (count _message's to recipients) > 20 then
            
-- Mail in 10.3.5 will hang if we ask it to get too many "to recipients"
            
set _recipients to {_message's first to recipient}
        
else
            
set _recipients to _message's to recipients
        
end if
        
repeat with _recipient in _recipients
            
copy _recipient's address to end of _addresses
        
end repeat
        
return _addresses
    
end tell
end toAddressesFromMessage

on ccAddressesFromMessage(_message)
    
tell application "Mail"
        
set _addresses to {}
        
if (count _message's cc recipients) > 20 then
            
set _recipients to {_message's first cc recipient}
        
else
            
set _recipients to _message's cc recipients
        
end if
        
repeat with _recipient in _recipients
            
copy _recipient's address to end of _addresses
        
end repeat
        
return _addresses
    
end tell
end ccAddressesFromMessage

on extraAddressesFromMessage(_message)
    
tell application "Mail"
        
set _addresses to {}
        
set _headerNames to {"envelope-to", "x-original-to", "x-envelope-to", "delivered-to"}
        
try
            
set _headers to _message's headers
            
repeat with _header in _headers
                
try
                    
set _name to name of _header
                    
ignoring case
                        
if _name in _headerNames then
                            
copy content of _header to end of _addresses
                        
end if
                    
end ignoring
                
on error
                    
-- Accessing headers seems broken on 10.7.0
                
end try
            
end repeat
        
end try
        
return _addresses
    
end tell
end extraAddressesFromMessage

on parseAddressesFromMessage(_message)
    
tell application "Mail"
        
set _addresses to {}
        
try
            
set _allHeaders to all headers of _message
            
repeat with _headerName in _headerNames
                
set AppleScript's text item delimiters to _headerName & ": "
                
try
                    
ignoring case
                        
set _value to paragraph 1 of text item 2 of _allHeaders
                    
end ignoring
                    
copy _value to end of _addresses
                
end try
            
end repeat
        
end try
        
return _addresses
    
end tell
end parseAddressesFromMessage

on lookupDefaults(_keys, _defaultValues)
    
tell application "SpamSieve"
        
try
            
set _result to {}
            
repeat with _i from 1 to count of _keys
                
set _key to item _i of _keys
                
set _defaultValue to item _i of _defaultValues
                
set _value to lookup single key _key default value _defaultValue
                
copy _value to end of _result
            
end repeat
            
return _result
        
on error -- SpamSieve 2.9.15 and earlier
            
return lookup keys _keys default values _defaultValues
        
end try
    
end tell
end lookupDefaults

-- Logging

on debugLog(_message)
    
if pEnableDebugLogging then my logToConsole(_message)
end debugLog

on logToConsole(_message)
    
set _logMessage to "SpamSieve [Apple Mail Move If Spam] MJTLog " & _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