Search code examples
c#outlookvstooutlook-addin

VSTO Outlook Add-In: Detecting email in a folder by email address through DASL table


I have the following code that takes an email address of received email once it was selected in Outlook Explorer and tries to find other emails that were received from this address. (I had historical reason to pass both email address and Outlook.MailItem to this function)

`       public (bool, int, bool) SearchAccountForEmail(string emailAddress, MailItem receivedEmail)
        {
          using (Log.VerboseCall())
          {
            bool found = false;
            int count = 0;
            bool domainNotSeen = false;

            Outlook.Account emailAccount = null;
            Outlook.Store store = null;
            Outlook.Folder rootFolder = null;

            try
            {
                emailAccount = receivedEmail.SendUsingAccount;
                Log.Verbose($"Finding {emailAddress} in account: {emailAccount}");

                if (emailAccount != null)
                {
                    // Get the root folder of the delivery store
                    store = emailAccount.DeliveryStore;
                    rootFolder = store.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) as Outlook.Folder;

                    byte[] mailItemEntryIdBytes = (byte[])mSelectedMailItem.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x0FFF0102");
                    string entryId = BitConverter.ToString(mailItemEntryIdBytes).Replace("-", "");

                    // Search folders in this account
                    (found, count) = IsEmailInFolder(rootFolder, emailAddress, entryId);
                    Log.Verbose($"Found Total: {count} emails, in folder {rootFolder.Name} for account {emailAccount.DisplayName}");

                    domainNotSeen = !IsDomainInFolder(rootFolder, emailAddress);
                    Log.Verbose($"Did not find additional domain in folder {rootFolder.Name} in account {emailAccount.DisplayName}: {domainNotSeen.ToString()}");
                }
                else
                {
                    Log.Verbose("Account not found.");
                }
            }
            catch(Exception ex)
            {
                Log.Verbose(ex);
            }
            finally
            {
                if (store != null) Marshal.ReleaseComObject(store);
                if (rootFolder != null) Marshal.ReleaseComObject(rootFolder);
                if (emailAccount != null) Marshal .ReleaseComObject(emailAccount);
            }

            return (found, count, domainNotSeen);
        }
    }

public (bool, int) IsEmailInFolder(Outlook.Folder folder, string emailAddress, string entryId)
{
using(Log.VerboseCall())
{
    string filter = $"@SQL=\"urn:schemas:httpmail:fromemail\" = '{emailAddress}'";
    string PR_LONGTERM_ENTRYID_FROM_TABLE = "http://schemas.microsoft.com/mapi/proptag/0x66700102";

    Table table = null;
    int rowCount = 0;
    QueryResponse qm = new QueryResponse();

    try
    {
        // Why to create Table every time we are searching for email instead of once:
        // - it is different for every folder
        // - the content change as new email arrive
        //
        // Try to retrieve a table of email items filtered by sender's email address
        table = folder.GetTable(filter, OlTableContents.olUserItems);
        if (table != null)
        {
            table.Columns.Add("SenderEmailAddress");
            table.Columns.Add("http://schemas.microsoft.com/mapi/proptag/0x66700102");

            rowCount = table.GetRowCount();
            Log.Verbose($"Found {rowCount} number of rows in table of in {folder.Name} folder, matching email {emailAddress} .");

            while (!table.EndOfTable)
            {
                Row row = table.GetNextRow();

                if (row != null)
                {
                    string senderEmail = row["SenderEmailAddress"].ToString();
                    if (String.Compare(senderEmail, emailAddress, true) == 0)
                    {
                        string hexEntryId = row.BinaryToString(PR_LONGTERM_ENTRYID_FROM_TABLE);

                        if (row != null)
                            Marshal.ReleaseComObject(row);

                        if (String.Compare(entryId, hexEntryId, StringComparison.OrdinalIgnoreCase) != 0)
                        {
                            Log.Verbose($"Folder: {folder.Name} - Found email with the same address {emailAddress} and different EntryID");
                            break;
                        }
                    }

                    if (row != null)
                        Marshal.ReleaseComObject(row);
                }
            }
        }
    }
    catch (Exception ex)
    {
        Log.Verbose(ex);
    }
    finally
    {
        if (table != null) Marshal.ReleaseComObject(table);
    }

    // Reduce the count - we don't count for the message itself, only for extra once found
    if (rowCount > 0)
        rowCount--;

    bool found = (rowCount > 0) ? true : false;
    if (found == true)
    {
        Log.Verbose($"Folder: {folder.Name} - {rowCount} emails found with the same address {emailAddress} and differnt EntryID");
        return (true, rowCount);
    }

    foreach (Folder subFolder in folder.Folders)
    {
        Log.Verbose($"Checking subFolder {subFolder.Name} for email address {emailAddress}");

        (bool ret, int count) = IsEmailInFolder(subFolder, emailAddress, entryId);
        if (ret == true)
        {
            rowCount += count;
        }
    }

    Log.Verbose($"Folder: {folder.Name} - {rowCount} emails found with the same address {emailAddress} and differnt EntryID");

    return (rowCount > 0) ? (true, rowCount) : (false, 0);
}

} `

As you can see the job is done in IsEmailInFolder function by using DASL table.

So here is the problem: For some email addresses it finds additional emails with the same 'To' address, and some emails (50%) it does not, and I have no idea why it gives me such bazaar results. Maybe DASL is not as reliable as I thought it was?

Obviously there must be at least one email with the input address - the email itself, and that's why I use EntryID to verify that I am not taking into account the email itself. This is recursive as I go through Inbox and all of its subfolders for a particular account.

I tried different schemes and debugging - it works on my machine but only finds 50% on client's machine.

EDIT: 5/12/2024

I guess I provided too much code. SO, here is the gist. The following code results in 'rowCount' 1, despite that emails from the same sender address are found in this folder (in addition to the received email which causes to count 1). This code works on my machine but not on client's machine, therefore I know that the DASL Property string is correct. Basically I want to know if there are additional emails from the same sender in the email folder.

table = folder.GetTable(filter, OlTableContents.olUserItems);
    if (table != null)
    {
        table.Columns.Add("SenderEmailAddress");
        table.Columns.Add("http://schemas.microsoft.com/mapi/proptag/0x66700102");

        rowCount = table.GetRowCount();
        Log.Verbose($"Found {rowCount} number of rows in table of in {folder.Name} folder, matching email {emailAddress} .");
}

Solution

  • The emails received by the customer are coming from variety of sources and in principle Dmitry is right - it is all about the filter. However, in OutlookSpy there are variety of entities, some for Exchange emails and some for POP and IMAP accounts, so to remove any doubt, here is what I used for a filter:

                    string filter = $@"@SQL=""http://schemas.microsoft.com/mapi/proptag/0x4025001F"" = '{emailAddress}'" // PR_LastModifierEmailAddr_W (EX format)
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x5D01001F"" = '{emailAddress}'" // PidTagSenderSmtpAddress_W
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x4023001F"" = '{emailAddress}'" // PR_CreatorEmailAddr_W
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x5D0A001F"" = '{emailAddress}'" // PR_CREATOR_SMTP_ADDRESS_W
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x0078001F"" = '{emailAddress}'" // PR_RCVD_REPRESENTING_EMAIL_ADDRESS_W (EX format)
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x0076001F"" = '{emailAddress}'" // PR_RECEIVED_BY_EMAIL_ADDRESS_W (EX format)
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x0C1F001F"" = '{emailAddress}'" // PR_SENDER_EMAIL_ADDRESS_W
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x0065001F"" = '{emailAddress}'" // PR_SENT_REPRESENTING_EMAIL_ADDRESS_W
                              + $@" OR  ""http://schemas.microsoft.com/mapi/proptag/0x5D02001F"" = '{emailAddress}'";// PR_SENT_REPRESENTING_SMTP_ADDRESS_W
    

    This one worked for everything. Also, as you can see eventually I used DASL and was getting the emails from the Table:

    table = folder.GetTable(filter, OlTableContents.olUserItems);
                        rowCount = table.GetRowCount();
                        Log.Verbose($"Found {rowCount} number of rows in table of in {folder.Name} folder, matching email {emailAddress} .");
    
                        table.Columns.Add("SenderEmailAddress");
                        table.Columns.Add(PR_LONGTERM_ENTRYID_FROM_TABLE);