Outlook - Filter Mailboxes
Summary: Periodically filter new messages in the inbox with SpamSieve.
Requires: SpamSieve, Outlook 365
Install Location: /Applications
Last Modified: 2022-06-10
Description
This script application is to help SpamSieve to automatically filter new messages in Outlook 2016 (also known as Outlook 365), since Outlook 2016 currently does not have the built-in ability to automatically apply AppleScripts (such as SpamSieve) to incoming messages.
For instructions on using this script, please see the Setting Up Outlook 365 section of the manual.
To enable debug logging, click this link. To disable debug logging, click this link. Debug logging is sent to Console and included in diagnostic reports.
To make the script check for new messages every 1 minute (the default), click this link. To make the script check for new messages every 15 seconds, click this link. To make the script check for new messages every 5 minutes, click this link.
Customizing the Script
If you need to customize the script, you will need to create your own application file rather than downloading the pre-made one. Open your edited script file Script Editor. From the File menu, choose Export…, set the File Format to Application, and make sure that Stay open after run handler is checked. Then click Save to create the Outlook - Filter Mailboxes.app application file.
Installation Instructions · Download in Compiled Format · Download in Text Format · Download in Application Format
Script
global
gDebug,
gSecondsBetweenChecks,
gInboxSpamSieveName,
gGoodCategoryName,
gGoodFolderName,
gMarkAsUnread
set
_keys
to
{"OutlookScriptDebug", "OutlookFilterMailboxesSecondsBetweenChecks", "OutlookFilterMailboxesInboxSpamSieveName", "OutlookFilterMailboxesGoodCategoryName", "OutlookFilterMailboxesGoodFolderName", "OutlookMarkAsUnread"}
set
_defaultValues
to
{
false
, 1 * 60, "InboxSpamSieve", "Good", "Inbox",
false
}
-- #17993: Cannot reproduce, but sometimes no error is reported instead of errAEEventNotPermitted, so try to prevent uninitalized values.
set
{
gDebug,
gSecondsBetweenChecks,
gInboxSpamSieveName,
gGoodCategoryName,
gGoodFolderName,
gMarkAsUnread}
to
_defaultValues
set
{
gDebug,
gSecondsBetweenChecks,
gInboxSpamSieveName,
gGoodCategoryName,
gGoodFolderName,
gMarkAsUnread}
to
my
lookupDefaultsAndHandleError(
_keys,
_defaultValues)
my
filterMailboxes()
on
idle
-- This is executed periodically when the script is run as a stay-open application.
my
filterMailboxes()
return
gSecondsBetweenChecks
end
idle
on
reopen
-- This is executed when you click on the Dock icon after the script app is already running.
my
filterMailboxes()
end
reopen
on
filterMailboxes()
if
application
"Microsoft Outlook"
is not
running
then
my
debugLog("Outlook is not running")
return
end
if
try
set
_mailboxes
to
my
mailboxesToFilter()
repeat
with
_mailbox
in
_mailboxes
set
_messages
to
my
messagesToFilterFromMailbox(
_mailbox)
if
gDebug
then
my
debugLog((
count
of
_messages) & " messages to filter in " &
describeFolder(
_mailbox))
end
if
repeat
with
_message
in
_messages
set
_score
to
scoreMessage(
_message)
if
_score ≥ 50
then
my
processSpamMessage(
_message,
_score)
else
my
processGoodMessage(
_message,
_score)
end
if
end
repeat
end
repeat
on
error
_error
number
_errorNumber
my
logToConsole("Error: " &
_error)
if
_errorNumber
is
-1743
then
-- errAEEventNotPermitted
set
_alertMessage
to
"You can give “Outlook Filter Mailboxes” access to control Outlook and SpamSieve from System Preferences > Security & Privacy > Privacy > Automation. (On macOS 13 Ventura this is in System Settings > Privacy & Security > Automation.) For more information, please see:
https://c-command.com/spamsieve/help/granting-automation-acc"
display alert
_error
message
_alertMessage
end
if
end
try
end
filterMailboxes
on
mailboxesToFilter()
tell
application
"Microsoft Outlook"
set
_result
to
every
mail folder
whose
name
starts with
gInboxSpamSieveName
if
_result
is
not
{}
then
return
_result
set
_result
to
{}
set
_accounts
to
imap accounts
&
pop accounts
&
exchange accounts
-- Just "accounts" does not work.
repeat
with
_account
in
_accounts
copy
_account's
inbox
to
end
of
_result
end
repeat
copy
inbox
to
end
of
_result
-- For Google accounts
end
tell
return
_result
end
mailboxesToFilter
on
messagesToFilterFromMailbox(
_mailbox)
tell
application
"Microsoft Outlook"
if
name
of
_mailbox
starts with
gInboxSpamSieveName
then
with
timeout
of
2 * 60
seconds
return
messages
of
_mailbox
-- Faster than checking unread and category
end
timeout
end
if
set
_messages
to
my
unreadMessagesFromMailbox(
_mailbox)
set
_result
to
{}
repeat
with
_message
in
_messages
if
my
shouldFilterMessage(
_message)
then
copy
_message
to
end
of
_result
end
if
end
repeat
return
_result
end
tell
end
messagesToFilterFromMailbox
on
unreadMessagesFromMailbox(
_mailbox)
set
_startDate
to
current date
tell
application
"Microsoft Outlook"
try
with
timeout
of
2 * 60
seconds
-- Using "whose" clause seems to make Outlook unresponsive.
set
_allMessages
to
messages
of
_mailbox
-- whose is read is false
end
timeout
set
_messages
to
{}
repeat
with
_message
in
_allMessages
if
_message's
is read
is
false
then
copy
_message
to
end
of
_messages
end
if
end
repeat
on
error
_error
number
_errorNumber
my
logToConsole("Outlook reported error “" &
_error & "” (number " &
_errorNumber & ") getting the messages from " &
my
describeFolder(
_mailbox))
return
{}
end
try
end
tell
set
_endDate
to
current date
set
_duration
to
_endDate -
_startDate
set
_statusMessage
to
"Outlook took " &
_duration & " seconds to get " & (
count
of
_messages) & " unread messages out of " & (
count
of
_allMessages) & " total messages from " &
my
describeFolder(
_mailbox)
if
_duration > 3
then
my
logToConsole(
_statusMessage)
else
my
debugLog(
_statusMessage)
end
if
return
_messages
end
unreadMessagesFromMailbox
on
shouldFilterMessage(
_message)
if
gGoodCategoryName
is
""
then
return
true
end
if
return
not
my
doesMessageHaveCategoryNamed(
_message,
gGoodCategoryName)
end
shouldFilterMessage
on
doesMessageHaveCategoryNamed(
_message,
_categoryName)
tell
application
"Microsoft Outlook"
set
_categories
to
_message's
category
repeat
with
_category
in
_categories
if
_category's
name
is
_categoryName
then
return
true
end
if
end
repeat
return
false
end
tell
end
doesMessageHaveCategoryNamed
on
scoreMessage(
_message)
tell
application
"Microsoft Outlook"
set
_source
to
_message's
source
end
tell
if
_source
is
missing value
then
my
logToConsole("Outlook could not get the source of message: " &
my
subjectOfMessage(
_message))
return
49
else
return
my
score(
_source)
end
if
end
scoreMessage
on
processSpamMessage(
_message,
_score)
my
debugLogMessage("Predicted Spam (" &
_score & ")",
_message)
if
my
isSpamScoreUncertain(
_score)
then
my
applyCategoryNamed(
_message, "Uncertain Junk")
else
my
applyCategoryNamed(
_message, "Junk")
end
if
my
moveToSpamFolder(
_message)
end
processSpamMessage
on
ensureUnread(
_message,
_beforeAfter)
if
not
gMarkAsUnread
then
return
tell
application
"Microsoft Outlook"
try
set
_read
to
_message's
is read
my
debugLogMessage("Read status " &
_beforeAfter & " moving: " &
_read,
_message)
if
_read
then
set
_message's
is read
to
false
set
_read
to
_message's
is read
my
debugLogMessage("Read status after marking unread: " &
_read,
_message)
end
if
on
error
_error
my
debugLog("Error ensuring message is unread: " &
_error)
end
try
end
tell
end
ensureUnread
on
moveToInbox(
_message)
tell
application
"Microsoft Outlook"
try
set
_inbox
to
inbox
of
_message's
account
on
error
_errorMessage
-- Will fail for new Google accounts that aren't scriptable.
my
logToConsole("Error getting inbox of message so using generic inbox: " &
_errorMessage)
try
my
ensureUnread(
_message, "before")
move
_message
to
inbox
on
error
_errorMessage
my
logToConsole("Error moving message to fallback generic inbox: " &
_errorMessage)
end
try
my
ensureUnread(
_message, "after")
return
end
try
my
debugLogMessage("Moving to inbox " &
my
describeFolder(
_inbox),
_message)
my
ensureUnread(
_message, "before")
move
_message
to
_inbox
my
ensureUnread(
_message, "after")
end
tell
end
moveToInbox
on
destinationFolderForMessage(
_message)
tell
application
"Microsoft Outlook"
set
_inboxFolderName
to
name
of
_message's
folder
set
AppleScript's
text item delimiters
to
""
set
_start
to
(
length
of
gInboxSpamSieveName) + 1
set
_destinationName
to
(
characters
_start
through
-1
of
_inboxFolderName)
as
Unicode text
set
_account
to
_message's
account
if
_account
is
missing value
then
return
mail folder
_destinationName
else
try
tell
_account
return
mail folder
_destinationName
end
tell
on
error
-- If message is POP, the above won't search nested On My Computer folders, but this will.
return
mail folder
_destinationName
end
try
end
if
end
tell
end
destinationFolderForMessage
on
moveToFolder(
_message)
tell
application
"Microsoft Outlook"
try
set
_folder
to
my
destinationFolderForMessage(
_message)
on
error
_errorMessage
my
logToConsole("Error getting destination for message, so moving to inbox: " &
_errorMessage)
my
moveToInbox(
_message)
return
end
try
my
debugLogMessage("Moving to folder " &
my
describeFolder(
_folder),
_message)
move
_message
to
_folder
end
tell
end
moveToFolder
on
moveToSpamFolder(
_message)
tell
application
"Microsoft Outlook"
set
_destFolder
to
my
junkFolderForMessage(
_message)
my
debugLogMessage("Moving to junk mailbox " &
my
describeFolder(
_destFolder),
_message)
my
ensureUnread(
_message, "before")
move
_message
to
_destFolder
try
if
_message's
folder
is
not
_destFolder
then
my
debugLogMessage("Removing to junk mailbox " &
my
describeFolder(
junk mail),
_message)
move
_message
to
junk mail
end
if
end
try
my
ensureUnread(
_message, "after")
end
tell
end
moveToSpamFolder
on
junkFolderForMessage(
_message)
tell
application
"Microsoft Outlook"
try
set
_destFolder
to
junk mail
of
_message's
account
if
_destFolder
is
not
missing value
then
return
_destFolder
end
try
try
set
_destFolder
to
junk mail
if
_destFolder
is
not
missing value
then
return
_destFolder
end
try
try
set
_destFolder
to
folder
"Junk E-mail"
of
_message's
account
if
_destFolder
is
not
missing value
then
return
_destFolder
end
try
try
set
_destFolder
to
folder
"Junk"
of
_message's
account
if
_destFolder
is
not
missing value
then
return
_destFolder
end
try
display dialog
"Could not find the “Junk E-mail” folder"
return
junk mail
end
tell
end
junkFolderForMessage
on
processGoodMessage(
_message,
_score)
my
debugLogMessage("Predicted Good (" &
_score & ")",
_message)
tell
application
"Microsoft Outlook"
set
_folderName
to
name
of
_message's
folder
if
_folderName
is
gInboxSpamSieveName
then
my
moveToInbox(
_message)
else
if
_folderName
starts with
gInboxSpamSieveName
then
my
moveToFolder(
_message)
else
if
gGoodCategoryName
is
not
""
then
my
applyCategoryNamed(
_message,
gGoodCategoryName)
end
if
if
gGoodFolderName
is
not
""
then
my
moveToFolderNamed(
_message,
gGoodFolderName)
end
if
end
if
end
tell
end
processGoodMessage
on
isSpamScoreUncertain(
_score)
tell
application
"SpamSieve"
set
_keys
to
{"Border", "OutlookUncertainJunk"}
set
_defaults
to
{75,
true
}
try
set
{
gUncertainThreshold,
gUncertainJunk}
to
lookup
keys
_keys
default values
_defaults
on
error
set
{
gUncertainThreshold,
gUncertainJunk}
to
_defaults
end
try
end
tell
return
_score <
gUncertainThreshold
and
gUncertainJunk
end
isSpamScoreUncertain
on
subjectOfMessage(
_message)
tell
application
"Microsoft Outlook"
try
return
(
_message's
subject)
as
Unicode text
on
error
return
"<Error getting subject of message id " &
_message's
id & ">"
end
try
end
tell
end
subjectOfMessage
on
moveToFolderNamed(
_message,
_folderName)
tell
application
"Microsoft Outlook"
try
move
_message
to
folder
_folderName
of
_message's
account
on
error
_error
-- Folder probably doesn't exist. Not sure how to create it.
my
logToConsole("Error moving to " &
_folderName & ": " &
_error)
end
try
end
tell
end
moveToFolderNamed
-- Categories
on
categoryForName(
_categoryName)
tell
application
"Microsoft Outlook"
try
-- "exists category _categoryName" sometimes lies
return
category
_categoryName
on
error
try
-- getting by name doesn't always work
repeat
with
_category
in
categories
if
_category's
name
is
_categoryName
then
return
_category
end
repeat
end
try
set
_category
to
make
new
category
with properties
{
name:
_categoryName}
if
_categoryName
is
gGoodCategoryName
then
set
_category's
color
to
{0, 0, 0}
end
if
end
try
return
category
_categoryName
end
tell
end
categoryForName
on
applyCategoryNamed(
_message,
_categoryName)
tell
application
"Microsoft Outlook"
set
_categoryToApply
to
my
categoryForName(
_categoryName)
set
_categories
to
_message's
category
repeat
with
_category
in
_categories
if
_category's
id
is
equal to
_categoryToApply's
id
then
return
end
repeat
set
category
of
_message
to
{
_categoryToApply} &
category
of
_message
end
tell
end
applyCategoryNamed
-- Logging
on
debugLogMessage(
_string,
_message)
if
not
gDebug
then
return
tell
application
"Microsoft Outlook"
try
set
_location
to
my
describeFolder(
_message's
folder
)
on
error
set
_location
to
"<error getting message's mailbox>"
end
try
set
_subject
to
my
subjectOfMessage(
_message)
end
tell
my
debugLog(
_string & ": [" &
_location & "] " &
_subject)
end
debugLogMessage
on
describeFolder(
_folder)
tell
application
"Microsoft Outlook"
set
_container
to
_folder's
container
-- For some reason, this doesn't work inside the "try"
try
set
_containerName
to
my
describeFolder(
_container)
on
error
try
return
name
of
_folder's
account
on
error
return
"[Unknown/OnMyComputer/Google]"
end
try
end
try
set
_folderName
to
_folder's
name
return
_containerName & "/" &
_folderName
end
tell
end
describeFolder
on
debugLog(
_message)
if
gDebug
then
my
logToConsole(
_message)
end
debugLog
on
logToConsole(
_message)
set
_prefix
to
"SpamSieve [Outlook Filter Mailboxes] MJTLog"
set
_logMessage
to
_prefix & " " &
_message
try
do shell script
"/usr/bin/logger -s " &
_logMessage's
quoted form
on
error
_error
number
_errorNumber
set
_alertMessage
to
"An error occurred while logging the message:" &
return &
return &
_message &
return &
return & "to Console. Error " &
_errorNumber & ": " &
return &
return &
_error &
return &
return & "This error message will dismiss itself after 10 seconds so that your filtering is not interrupted."
display alert
_prefix
message
_alertMessage
giving up after
10
end
try
end
logToConsole
on
lookupDefaultsAndHandleError(
_keys,
_defaultValues)
try
return
my
lookupDefaults(
_keys,
_defaultValues)
on
error
_error
number
_errorNumber
if
_errorNumber
is
-1743
then
-- errAEEventNotPermitted
my
logToConsole("Error looking up defaults: " &
_error)
set
_alertMessage
to
"You can give “Outlook Filter Mailboxes” access to control Outlook and SpamSieve from System Preferences > Security & Privacy > Privacy > Automation. (On macOS 13 Ventura this is in System Settings > Privacy & Security > Automation.) For more information, please see:
https://c-command.com/spamsieve/help/granting-automation-acc"
display alert
_error
message
_alertMessage
end
if
error
_error
number
_errorNumber
end
try
end
lookupDefaultsAndHandleError
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
try
return
lookup
keys
_keys
default values
_defaultValues
on
error
_errorMessage
my
logToConsole("Error getting fallback defaults: " &
_errorMessage)
return
_defaultValues
end
try
end
try
end
tell
end
lookupDefaults
on
score(
_source)
my
debugLog("Begin scoring message of length " & (
count
of
_source))
tell
application
"SpamSieve"
set
_result
to
score
message
_source
end
tell
my
debugLog("End scoring message of length " & (
count
of
_source))
return
_result
end
score