|
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
|
|
Posted 06 Dec, 2021 09:50:50
|
|
Top
|
|