Search code examples
emailoutlookoutlook-addinadd-in

Why does my Outlook add in randomly stop working?


I have a plugin running in Outlook 2013 that is intended to filter out spam. It looks at incoming emails, runs through a half-dozen heuristics, and if 1 or 2 of them return positive, it moves the mail to another folder for inspection; if three or more are tripped, it permanently deletes the mail.

My problem is that it randomly stops working. The add in isn't marked as "disabled" or "inactive"; it just doesn't work anymore. Disabling it and then enabling it brings it back on line, and then it will work fine, including on the mail that it just failed on. And then it will fail randomly again a few messages later. No error is being raised as far as I can see; no exception is being thrown.

I feel like this has something to do with Outlook's limits for add-ins, since it is (a) non-deterministic and (b) seems to happen more often the more checks I do. But nothing I am doing is very heavy; most of it is just checking things in the email address, subject, and headers, or looking for key phrases in the body. Eyeballing it, every message appears to be processed in just a fraction of a second. I guess the first question is, is there a way to see Outlook's decision process on this, i.e. the specific perf statistics it is tracking? (ETA: Does Outlook enforce its restrictions when the add-in is invoked, or just at startup? Everything I can find only refers to startup.)

No single one of the checks seems to be responsible; I've disabled each one individually and it keeps happening.

Here's the central code. SpamMarkerCheckList is a list of delegates for the individual heuristic checks. ClearAllMailMarkers() does things like mark the mail as read, set the priority to normal, remove any flags, etc. headers is a Dictionary with the header names as the keys and lists of strings as the values.

    private static void FilterInbox(object mailItem)
    {
        try
        {
            if (mailItem != null)
            {
                var mail = (Outlook.MailItem)mailItem;
                var markers = SpamMarkersCount(mail, 3);
                if (markers > 2)
                {
                    PermanentlyDeleteEmail(mail);
                }
                else if (markers > 0)
                {
                    ClearAllMailMarkers(mail);
                    mail.Move(_sequesteredFolder);
                }
            }
        }
        catch (Exception e)
        {
            MessageBox.Show("FilterSpam caught unexpected exception -- " + e);
        }
    }

    private static int SpamMarkersCount(Outlook.MailItem mail, int threshold)
    {
        var spamMarkerCount = 0;
        var headers = GetHeaderProperties(mail);
        var emailAddressList = BuildAddressList(mail, headers);
        var fullStringList = GetAllStrings(mail, headers);

        foreach (var spamMarkerCheck in SpamMarkerCheckList)
        {
            if (spamMarkerCheck(mail, headers, emailAddressList, fullStringList))
            {
                spamMarkerCount++;
                if (spamMarkerCount >= threshold)
                {
                    return spamMarkerCount;
                }
            }
        }

        return spamMarkerCount;
    }

The checks I am doing are:

  • Non-ASCII characters in the sender's name or address (e.g. hearts, shopping carts, etc.)
  • Indicators in the headers, like the presence of List-Unsubscribe or failed SPF, DKIM, or DMARC authentication
  • Email addresses that are badly formed or missing
  • If it was sent from a fake domain (via doing a DNS lookup)
  • The presence of the user's email alias in the subject or sender name ("delius1967 is a winner!")
  • If it was sent from a known blocked list of domains
  • If it contains specific phrases (e.g. "this is an advertisement")

Solution

  • I found the proximate cause: garbage collection. After running in the debugger for quite a while, I noticed that the add-in stopped working immediately after a GC event; several more runs through show that this happens consistently.

    The ultimate cause was in how I was associating the function with the folder:

    private static Outlook.Account emailStore; // outside ThisAddIn_Startup
    [...]
    emailStore.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Items.ItemAdd += FilterInbox;
    

    For reasons I do not fully understand, that causes a problem after GC, but this does not:

    private static Outlook.Items emailItems; // outside ThisAddIn_Startup
    [...]
    emailItems = emailStore.DeliveryStore.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Items;
    emailItems.ItemAdd += FilterInbox;
    

    I get why having it as static is critical, but I don't understand why it makes a difference in the assignment. Anyone with a deeper understanding of C# objects who can explain, I would love to hear it.