OUTLOOK ADDIN

Add-in Express™ Support Service
That's what is more important than anything else

OUTLOOK ADDIN
How to search for the original email from a Reply 
David Wisniewski




Posts: 39
Joined: 2015-11-20
Office 365, Outlook Addin. I would like to create a macro that a user can execute to send a reply message that they have composed in response to an email. The user would have already clicked reply to an email in their inbox, and have already typed their response. The macro that they execute would send the reply, and then move the original email out of the inbox and into a different folder. I have had this macro working back in 2015 using the Advanced Search functionality where I search using the MailItem.ConversationTopic and MailItem.ConversationIndex to hunt for the original email. Now that we're about to be in 2022 this isn't working so great anymore and is causing a ton of issues with people who have large mailboxes. There has to be better criteria to use to search, or a better method maybe? The user may have several emails open so there's no other way I can think of to identify the original email for the reply they are currently typing except by using Advanced Search.

Current Code is below:

Friend Sub MoveOriginalEmail(strConversationTopic As String, strConversationIndex As String, strFolderName As String)
  'make safe by escaping apostrophes
  Dim strOriginalTopic As String = strConversationTopic.Replace("'", "''")
  Dim strOriginalIndex As String = Left(strConversationIndex, (Len(strConversationIndex) - 10))
  Dim strCriteria As String = "urn:schemas:mailheader:thread-topic = '" & strConversationTopic & "'"
  Dim strSearchTag As String = "DLW:" & AdvancedSearchAction.MoveMail & ":" & strOriginalIndex & ":" & strFolderName
  Dim comAdvancedSearch As Outlook.Search = OutlookApp.AdvancedSearch("'\username@gmail.com'", strCriteria, 
  True, strSearchTag)
  ReleaseCOM(comAdvancedSearch)
End Sub

Private Sub AdxOutlookAppEvents1_AdvancedSearchComplete(sender As Object, hostObj As Object) Handles AdxOutlookAppEvents1.AdvancedSearchComplete
  'handle the search object
  Dim oSearch As Outlook.Search = CType(hostObj, Outlook.Search)
  Dim strSearchTag As String = oSearch.Tag
  'ensure that code is not executed unless initiated by me
  If Left(strSearchTag, 3) <> "DLW" Then
    Exit Sub
  End If
  Dim asaActionType As AdvancedSearchAction = strSearchTag.Split(":")(1)
  Dim strOriginalIndex As String = strSearchTag.Split(":")(2)
  Dim strFolder As String = strSearchTag.Split(":")(3)
  Dim comResults As Outlook.Results = oSearch.Results
  Dim intResults As Integer = comResults.Count
  If intResults = 0 Then
    ReleaseCOM(comResults)
    Exit Sub
  End If
  Dim comMailItem As Outlook.MailItem = Nothing
  Dim strIndex As String = ""
  For iCounter As Integer = 1 To intResults
    comMailItem = comResults.Item(iCounter)
    'using try to avoid comexception when attempting to get index of mailitem being sent
    Try
      strIndex = comMailItem.ConversationIndex
    Catch ex As COMException
      'set index to empty, as this email was being sent so cannot be the one we're looking for
      strIndex = ""
    End Try
    If strIndex = strOriginalIndex Then
      Exit For
    Else
      ReleaseCOM(comMailItem)
    End If
  Next
  ReleaseCOM(comResults)
  'if mailitem was not found then just exit
  If IsNothing(comMailItem) Then
    Exit Sub
  Else
    comMailItem.UnRead = False
    comMailItem.Save()
  End If
  Select Case asaActionType
    Case AdvancedSearchAction.MoveMail
      'if folder is junk then check to see if item is already in junk.
      'if so then toggle feature is needed to move it back to inbox
      Dim comParent As Outlook.Folder = Nothing
      If strFolder = "Junk" Then
        comParent = comMailItem.Parent
        If comParent.Name = strFolder Then
          strFolder = "Inbox"
        End If
        ReleaseCOM(comParent)
      End If
      comParent = comMailItem.Parent
      Dim strParentFolder As String = comParent.Name
      ReleaseCOM(comParent)
      If strParentFolder <> strFolder Then
        Dim comMapiFolder As Outlook.MAPIFolder = fldRoot.Folders(strFolder)
        'set global variables to allow executing macros later to open last mail moved to previn
        If strFolder = "Previn" Then
          strPrevin_CIndex = comMailItem.ConversationIndex
          strPrevin_CTopic = comMailItem.ConversationTopic
        End If
        Dim comMovedMailItem As Outlook.MailItem = comMailItem.Move(comMapiFolder)
        ReleaseCOM(comMovedMailItem)
        ReleaseCOM(comMapiFolder)
      End If
    Case AdvancedSearchAction.OpenMail
      comMailItem.Display()
    End Select
    ReleaseCOM(comMailItem)
End Sub

Public Sub ReleaseCOM(ByRef comObject As Object)
  If Not IsNothing(comObject) Then
    Marshal.ReleaseComObject(comObject)
    comObject = Nothing
  End If
End Sub
Posted 02 Dec, 2021 17:56:22 Top
Andrei Smolin


Add-in Express team


Posts: 18830
Joined: 2006-05-11
Hello David,

I suggest a different approach.

You can intercept these Ribbon commands (IdMso): Reply, ReplyAll, and Forward. In the OnAction event of the corresponding ADXRibbonCommand components, invoke a method that does the following:
- it retrieves the current context via control.Context (control is a parameter of the OnAction event handler)
- if the context is Nothing (null in C#) it completes
- If TypeOf context Is Outlook.Explorer, it gets Explorer.Selection, gets the first item, cast it to Outlook.MailItem, retrieves all relevant information from the mail item, creates an object holding that information and adds that object to a collection/dictionary
- If TypeOf context Is Outlook.Inspector, it gets Inspector.CurrentItem, cast it to Outlook.MailItem, retrieves all relevant information from the mail item (ConversationIndex, EntryID, etc.) , creates an object holding that information and adds that object to a collection/dictionary

To send the email, you call another method that retrieves the relevant information from the email being sent and scans the collection/dictionary looking, gets the original email, processes it, deletes the corresponding object from the collection/dictionary, and sends the current email.

What do you think?

Regards from Poland (CET),

Andrei Smolin
Add-in Express Team Leader
Posted 03 Dec, 2021 04:19:17 Top
David Wisniewski




Posts: 39
Joined: 2015-11-20
Thank you. That's a great idea. at the end when the user sends the reply and I find the information in my collection regarding the parent (original email), before I remove that object from the collection, how would I obtain a handle to the original email in order to move it from its original folder in outlook to a different folder in outlook? Would I still need to use the Advanced Search method in outlook and then in the async event response perform the move? My goal is to stop using the Advanced Search method if possible, it's causing a multitude of issues. Would it work for me to maintain in memory a collection of actual MailItems objects for the actual original emails when the user initiates a reply to one of them, that way I could move those email objects by seaching in my collection instead of using the advanced search? The problem I'm encoutering is how to get a handle to the orginal mailitem without using Advanced Search.
Posted 03 Dec, 2021 09:45:15 Top
Andrei Smolin


Add-in Express team


Posts: 18830
Joined: 2006-05-11
You call OutlookApp.Session.GetItemFromId() and pass it the EntryId of that item. Then you cast the result to MailItem and call MailItem.Move(). Note that Move() returns a COM object representing the item in the target location and you need to release it along with the COM object representing item in its original location.

With this approach, you won't use AdvancedSearch.
You would need to store only those MailItems for which the user clicks Reply (ReplyAll, Forward).

Regards from Poland (CET),

Andrei Smolin
Add-in Express Team Leader
Posted 03 Dec, 2021 09:56:46 Top
David Wisniewski




Posts: 39
Joined: 2015-11-20
You are absolutely incredible!
Posted 03 Dec, 2021 10:47:04 Top
David Wisniewski




Posts: 39
Joined: 2015-11-20
What I ended up doing, instead of maintaining a running list in memory of open reply mailitems with their parent email id, I've saved the entryID of the original email as a user property of the reply mailitem (called O-EntryID) in case I need to reference the orignal email via code in the future for the sent item, and also temporarily stored it in a class level variable in the OutlookItemEvents class in case i need to move the email on the Send event. See sample code below where I'm simply displaying the original email after the reply is sent, to demonstrate that it works. I'm disappointed that outlook itself doesn't link a reply to the parent email in a property that is easily utilized, or if it does I can't find it.


Public Class OutlookItemEventsClass1
    Inherits AddinExpress.MSO.ADXOutlookItemEvents

    Private OutlookApp As Outlook._Application = OutlookMacrosV2.AddinModule.CurrentInstance.OutlookApp
    Private mOEntryID As String

    Public Overrides Sub ProcessReply(ByVal Response As Object, ByVal E As AddinExpress.MSO.ADXCancelEventArgs)
        DocumentOriginalEmailEntryID(Response)
    End Sub

    Public Overrides Sub ProcessSend(ByVal E As AddinExpress.MSO.ADXCancelEventArgs)
        Dim comNameSpace As Outlook.NameSpace = OutlookApp.Session
        Dim comMailItem As Outlook.MailItem = comNameSpace.GetItemFromID(mOEntryID)
        comMailItem.Display()
        ReleaseCOM(comMailItem)
        ReleaseCOM(comNameSpace)
    End Sub

    Private Sub DocumentOriginalEmailEntryID(ByRef comMailItemReply As Outlook.MailItem)
        Dim comExplorer As Outlook.Explorer = OutlookApp.ActiveExplorer
        Dim comSelection As Outlook.Selection = comExplorer.Selection
        Dim comMailItem As Outlook.MailItem = comSelection(1)
        Dim comUserProperties As Outlook.UserProperties = comMailItemReply.UserProperties
        Dim comUserProperty As Outlook.UserProperty
        comUserProperty = comUserProperties.Add("O-EntryID", Outlook.OlUserPropertyType.olText, False, 1)
        comUserProperty.Value = comMailItem.EntryID
        mOEntryID = comMailItem.EntryID
        comMailItemReply.Save()
        ReleaseCOM(comUserProperty)
        ReleaseCOM(comUserProperties)
        ReleaseCOM(comMailItem)
        ReleaseCOM(comSelection)
        ReleaseCOM(comExplorer)
    End Sub

    Public Sub ReleaseCOM(ByRef comObject As Object)
        If Not IsNothing(comObject) Then
            Marshal.ReleaseComObject(comObject)
            comObject = Nothing
        End If
    End Sub
End Class
Posted 06 Dec, 2021 04:48:12 Top
Andrei Smolin


Add-in Express team


Posts: 18830
Joined: 2006-05-11
Hello David,

It isn't recommended to send UserProperties out. Although I've used it in https://www.add-in-express.com/creating-addins-blog/2020/02/26/solve-synchronization-conflict-when-modifying-outlook-item-itemadd-event/, I don't like such solutions. I wrote about it:

I admit that the idea made me uneasy: it contradicts to what I know about sending UserProperties. It suffices to say: never in my life I have recommended anyone to send UserPorperties out. There are stories that start with sending out an Outlook item with a UserProperty on it. Typically, these UserProperties appeared missing on the receiving end (https://stackoverflow.com/questions/58517786/lose-userproperties-of-taskrequestitem-when-it-is-passed-to-outlook-application). Sometimes, they produced a strange attachment named winmail.dat (find a solution on https://social.msdn.microsoft.com/Forums/en-US/07458291-73d6-460c-8c48-b54f757dd4f8/update-message-on-itemsend?forum=outlookdev). Also, it is good to know that in older Outlook versions, UserProperties were transferred differently (https://support.microsoft.com/en-gb/help/907985/changes-to-custom-properties-in-outlook).

Still, if it works for you *and if* customers don't notice any strange attachments sent with their reply emails, then it is probably okay.

Regards from Poland (CET),

Andrei Smolin
Add-in Express Team Leader
Posted 06 Dec, 2021 09:50:50 Top