Outlook - Filter Mailboxes

Summary: Periodically filter new messages in the inbox with SpamSieve.
Requires: SpamSieve, Outlook 2016
Install Location: ~/Library/Scripts/Applications/Microsoft Outlook
Last Modified: 2017-05-04

Description

As described in this forum post, Outlook 2016 currently does not have the ability to automatically apply AppleScripts (such as SpamSieve) to incoming messages. The manual describes how you can manually filter the selected messages through SpamSieve using the SpamSieve - Move If Spam command.

This script will automatically filter the new messages in the inbox through SpamSieve every five minutes (duration configurable using pMinutesBetweenChecks below). To set it up, click “Download in Application Format” below to get the Outlook - Filter Mailboxes.app application file.

To begin automatically filtering new messages in the inbox, double-click the Outlook - Filter Mailboxes.app application to launch it. You could also set this as a Login Item in System Preferences.

Each time it’s run, the application will check the inboxes (mailbox names configurable using mailboxNamesToFilter()) for unread messages that have not been previously filtered. Any spam messages will be moved to the “Junk E-mail” mailbox. Any good messages will be marked with category “Good” so that they are not processed again the next time the script runs.

The script will report any errors in Console with the prefix “SpamSieve [Outlook Filter Mailboxes]”.

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.

Note: Because the script scans your entire inbox, it can take a long time if there are lots of messages in the inbox. You can speed it up by moving older messages to a separate folder, e.g. Archive. You can also create a folder called “InboxClean” in each account and set the pGoodFolderName option to InboxClean. This will automatically move any new, good messages to that folder so that the actual inbox remains empty (and thus quick to process).

Caveat: By default, the application only scans your inboxes for new messages, so it will not filter messagess that your Outlook rules moved to another mailbox. You can add addtional mailbox names to mailboxNamesToFilter() below, although this will add some overhead as there will be more messages to scan each time the script runs.

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

Script

property pMinutesBetweenChecks : 5
property pGoodCategoryName : "Good"
property pGoodFolderName : "" -- Set to "InboxClean" to move good messages out of the inbox
property pEnableDebugLogging : false

on mailboxNamesToFilter()
    
return {"Inbox", "INBOX", "Indbakke", "innboks", "Posteingang", "Boite de reception", "Inkorg"}
end mailboxNamesToFilter

-- Do not modify below this line.

on run
    
-- This is executed when you run the script directly.
    
my filterMailboxes()
end run

on idle
    
-- This is executed periodically when the script is run as a stay-open application.
    
my filterMailboxes()
    
return 60 * pMinutesBetweenChecks
end idle

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 pEnableDebugLogging then
                
my debugLog((count of _messages) & " messages to filter in " & locationFromMailbox(_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
        
my logToConsole("Error: " & _error)
    
end try
end filterMailboxes

on mailboxesToFilter()
    
set _result to {}
    
set _names to my mailboxNamesToFilter()
    
repeat with _name in _names
        
tell application "Microsoft Outlook"
            
considering case
                
set _matches to (every mail folder whose name is _name)
                
set _result to _result & _matches
            
end considering
        
end tell
    
end repeat
    
return _result
end mailboxesToFilter

on messagesToFilterFromMailbox(_mailbox)
    
tell application "Microsoft Outlook"
        
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
                
set _messages to messages of _mailbox whose is read is false
            
end timeout
        
on error _error number _errorNumber
            
my logToConsole("Outlook reported error “" & _error & "” (number " & _errorNumber & ") getting the messages from " & my locationFromMailbox(_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 from " & my locationFromMailbox(_mailbox)
    
if _duration > 5 then
        
my logToConsole(_statusMessage)
    
else
        
my debugLog(_statusMessage)
    
end if
    
return _messages
end unreadMessagesFromMailbox

on shouldFilterMessage(_message)
    
if pGoodCategoryName is "" then
        
return true
    
end if
    
return not my doesMessageHaveCategoryNamed(_message, pGoodCategoryName)
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
        
tell application "SpamSieve"
            
return score message _source
        
end tell
    
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)
    
my debugLogMessage("Moved message to junk mailbox", _message)
end processSpamMessage

on moveToSpamFolder(_message)
    
tell application "Microsoft Outlook"
        
set _destFolder to my junkFolderForMessage(_message)
        
move _message to _destFolder
        
try
            
if _message's folder is not _destFolder then
                
move _message to junk mail
            
end if
        
end try
    
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)
    
if pGoodCategoryName is not "" then
        
my applyCategoryNamed(_message, pGoodCategoryName)
    
end if
    
if pGoodFolderName is not "" then
        
my moveToFolderNamed(_message, pGoodFolderName)
    
end if
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 pGoodCategoryName 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 pEnableDebugLogging then return
    
tell application "Microsoft Outlook"
        
try
            
set _location to my locationFromMailbox(_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 locationFromMailbox(_mailbox)
    
tell application "Microsoft Outlook"
        
try
            
set _accountName to name of _mailbox's account
        
on error
            
set _accountName to "On My Mac"
        
end try
        
set _mailboxName to name of _mailbox
        
return _accountName & " > " & _mailboxName
    
end tell
end locationFromMailbox

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

on logToConsole(_message)
    
set _prefix to "SpamSieve [Outlook Filter Mailboxes]"
    
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