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
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