Apple Mail - Remote Training
Summary: Training script for setting up a spam filtering drone with Apple Mail.
Requires: SpamSieve, Apple Mail
Install Location: ~/Library/Application Scripts/com.apple.mail or ~/Library/Scripts/Applications/Mail/
Last Modified: 2022-12-27
Description
With SpamSieve 3.2, this script is no longer needed because its functionality is built directly into the app.
Please see the Setting Up a Spam Filtering Drone section of the manual for more information about what this script does. If you’re using the standard setup where spam messages are moved to the Junk mailbox, you can simply click here to download the script. If you’re using an older setup with the Spam mailbox, you’ll need to enter the names of the IMAP and Exchange accounts that you want the drone to operate on. For example, if you have one account named “Bob iCloud” the modified part of the script would be:
on accountNamesForDrone()
return {"Bob iCloud"}
end accountNamesForDrone
For two accounts, you would instead enter something like:
on accountNamesForDrone()
return {"Bob iCloud", "Bob Gmail"}
end accountNamesForDrone
To test 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.
Normally, the script will run when Mail receives a new message in the inbox and applies the “Remote Training” 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 remote training to happen on a more predictable schedule. 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 the remote training should occur.
The first time you run the script, it may take a long time if there are lots of messages in the training mailboxes. It will wait for three minutes for Mail to fetch the messages before giving up. You can speed this up by manually training those messages.
The script may also take a long time to run if the training mailboxes contain lots of messages that Mail has deleted but not yet purged. You can speed this by up deleting and recreating the training mailboxes to get rid of those messages.
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 pColorSpamMessages : true
property pFlagSpamMessages : false
property pMarkGoodMessagesUnread : false
property pMoveBySettingMailbox : true
property pSpamMailboxName : "Spam"
property pEnableDebugLogging : false
global useJunkMailbox
on accountNamesForDrone()
-- Enter your account names here. If you have more than one, separate with commas: {"Account 1", "Account 2"}
-- The account name comes from the "Description" field in the Accounts tab of Mail's preferences.
return {"Account 1"}
end accountNamesForDrone
on spamMailboxNamesByAccount()
-- You can specify pairs here, e.g. {{"Work Account", "Junk"}, {"Personal Account", "Spam"}}
-- to have different spam mailbox names for each account. If an account is not specified,
-- it defaults to pSpamMailboxName.
return {}
end spamMailboxNamesByAccount
on hostNameForDrone()
return ""
-- Remove the above line if iCloud rule syncing is working incorrectly,
-- so that your remote training rule cannot be made inactive on the
-- non-drone Macs.
set {_host} to my lookupDefaults({"AppleMailEnabledHostName"}, {""})
return _host
end hostNameForDrone
on debugLog(_message)
if pEnableDebugLogging then my logToConsole(_message)
end debugLog
on logToConsole(_message)
set _logMessage to "SpamSieve [Apple Mail Remote Training MJTLog] " & _message
do shell script "/usr/bin/logger -s " & _logMessage's quoted form
end logToConsole
on run
-- This is executed when you run the script directly.
my doRemoteTraining()
end run
on idle
-- This is executed periodically when the script is run as a stay-open application.
my doRemoteTraining()
return 60 * 5 -- Run again in 5 minutes.
end idle
on shouldDisableOnThisMac()
set _droneHost to my hostNameForDrone()
if _droneHost is "" then return false
set _currentHost to do shell script "/usr/bin/uname -n"
if _droneHost is _currentHost then return false
my debugLog("Drone disabled on host: " & _currentHost)
return true
end shouldDisableOnThisMac
using terms from application "Mail"
on perform mail action with messages _messages
-- This is executed when Mail runs the rule.
my doRemoteTraining()
end perform mail action with messages
end using terms from
on doRemoteTraining()
if application "Mail" is not running then return
if my shouldDisableOnThisMac() then return
tell application "SpamSieve"
set useJunkMailbox to lookup single key "AppleMailUseJunkMailbox" without default value
end tell
try
tell application "Mail" to get version
on error _error number _errorNumber
if _errorNumber is -1743 then -- errAEEventNotPermitted
set _alertMessage to "You can give “Apple Mail - Remote Training” access to control Mail and SpamSieve from System Preferences > Security & Privacy > Privacy > Automation. For more information, please see:
https://c-command.com/spamsieve/help/security-privacy-acce"
display alert _error message _alertMessage
end if
end try
tell application "Mail"
set _accounts to my accountsToCheck()
repeat with _account in _accounts
my debugLog("Checking account: " & _account's name)
try
my trainMessagesInAccount(_account)
on error _error
my logToConsole("Error training from account “" & _account's name & "”: " & _error)
end try
end repeat
end tell
end doRemoteTraining
on accountsToCheck()
tell application "Mail"
set _result to {}
if useJunkMailbox then
repeat with _account in accounts
try
if _account's enabled then
my findMailbox("TrainSpam", _account)
copy _account to end of _result
end if
on error
-- Avoid creating a mailbox
end try
end repeat
else
repeat with _accountName in my accountNamesForDrone()
set _account to account _accountName
copy _account to end of _result
end repeat
end if
return _result
end tell
end accountsToCheck
on trainMessagesInAccount(_account)
tell application "Mail"
set _messages to my messagesFromMailbox("TrainSpam", _account)
repeat with _message in _messages
set _source to my sourceFromMessage(_message)
tell application "SpamSieve" to add spam message _source
set _message's junk mail status to true
if pColorSpamMessages then
set _message's background color to blue
end if
if pFlagSpamMessages then
set _message's flag index to 6
end if
if pMarkSpamMessagesRead then
set _message's read status to true
end if
my moveMessage(_message, my spamMailboxForAccount(_account), true)
end repeat
set _messages to my messagesFromMailbox("TrainGood", _account)
repeat with _message in _messages
set _source to my sourceFromMessage(_message)
tell application "SpamSieve" to add good message _source
set _message's junk mail status to false
if pColorSpamMessages then
set _message's background color to none
end if
if pFlagSpamMessages then
set _message's flag index to -1
end if
if pMarkGoodMessagesUnread then
set _message's read status to false
end if
my moveMessage(_message, my inboxForAccount(_account), false)
end repeat
end tell
end trainMessagesInAccount
on messagesFromMailbox(_mailboxName, _account)
tell application "Mail"
set _accountName to name of _account
try
set _mailbox to my findMailbox(_mailboxName, _account)
on error
tell _account
make new mailbox with properties {name:_mailboxName}
end tell
set _mailbox to mailbox _mailboxName of _account
end try
my debugLog(my makeLogMessage("Getting messages in mailbox", _mailbox, "This can take a long time if there are many messages."))
with timeout of 3 * 60 seconds
set _messages to messages of _mailbox whose deleted status is false
end timeout
my debugLog(my makeLogMessage("Messages in mailbox", _mailbox, count of _messages))
return _messages
end tell
end messagesFromMailbox
on findMailbox(_mailboxName, _account)
tell application "Mail"
try
return mailbox _mailboxName of _account
on error
-- my debugLog("Looking for nested mailbox " & _mailboxName & " in account " & _account's name)
set _result to my findNestedMailbox(_mailboxName, mailboxes of _account)
if _result is not missing value then
my debugLog("Found nested mailbox " & _mailboxName & " in account " & _account's name)
return _result
end if
my debugLog("No nested mailbox " & _mailboxName & " in account " & _account's name)
error "No mailbox named " & _mailboxName
end try
end tell
end findMailbox
on findNestedMailbox(_mailboxName, _mailboxes)
tell application "Mail"
repeat with _mailbox in _mailboxes
if name of _mailbox is _mailboxName then
return _mailbox
end if
end repeat
repeat with _mailbox in _mailboxes
set _result to my findNestedMailbox(_mailboxName, mailboxes of _mailbox)
if _result is not missing value then
return _result
end if
end repeat
return missing value
end tell
end findNestedMailbox
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, _isSpam)
tell application "Mail"
if not useJunkMailbox then
my debugLog(my makeLogMessage("Moving message to", _mailbox, _message's subject))
set _message's mailbox to _mailbox
return
end if
if _isSpam then
my debugLog("Moving message to Junk: " & _message's subject)
move _message to junk mailbox
else
if pMoveBySettingMailbox then
my debugLog(my makeLogMessage("Moving message to", _mailbox, _message's subject))
set _message's mailbox to _mailbox
else
-- Not used by default because for many users copies rather than moves messages that Mail reports are in Gmail's All Mail, however some users report that this works better (#kZMU4ZD8QOzpruuManwLChA).
my debugLog("Moving message to Inbox: " & _message's subject)
move _message to inbox
end if
end if
end tell
end moveMessage
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
on spamMailboxForAccount(_account)
if useJunkMailbox then return missing value
tell application "Mail"
repeat with _pair in my spamMailboxNamesByAccount()
if item 1 of _pair is name of _account then
set _name to item 2 of _pair
return mailbox _name of _account
end if
end repeat
return mailbox pSpamMailboxName of _account
end tell
end spamMailboxForAccount
on inboxForAccount(_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 inboxForAccount
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