Problem altering/saving moved MailItems in Outlook VBA
I am writing some mail filters using VBA for Outlook 2003, and I have hit what I believe is a bug in Outlook, but the workaround is not apparent to me.
When email arrives, I am processing it using my code, launched from the built-in Outlook Rules as a script. This works fine. The problem arises when I choose to run my rules on, say, another folder.
I found that any items of mail that have been flagged as complete cannot be moved, and I encounter the following error message:
"an unsent message cannot be flagged complete"
This is bizarre, because this is incoming mail, in either the Inbox itself, or a sub-folder. So, there should be no cause for this error - especially as my code is not flagging the mail item - the mail item is already flagged as complete.
To reproduce this error message, simply try programmatically moving a received email message from one folder to another. You will encounter the error for any completed items.
I performed a Google search but could not find a workaround to this problem. One way to avoid the error is to unflag the mail item, but this is obviously not the desired final state... the code I am working on currently unflags the mail item, moves it, and then attempts to reflag the item again and save it. However, I am experiencing a lot of difficulty with this, because once the mail item has been moved, it seems to disappear from the clutches of my code.
Here is an example:
| |
Function MoveEmailItemToFolder(objEmailMessageAs MailItem, strFolderAsString) '- ' Description '- ' This function moves the supplied email message to the specified folder. On Error GoTo ErrorHandler '- ' Declarations '- Dim objFolder As MAPIFolder ' Destination folder Dim strEntryID As String ' Email id '- ' Initialisation '- Set objFolder = Outlook.Application.GetNamespace("MAPI").GetFolderFromID(colFolders.Item(strFolder)) strEntryID = objEmailMessage.EntryID '- ' Code '- If objFolder Is Nothing Then MsgBox ("The specified folder (below) is invalid and could not be referenced" _ & Chr(13) & Chr(13) & strFolder) Else Debug.Print " ...moving" If objEmailMessage.FlagStatus = olFlagComplete Then Debug.Print " ...unflagging" objEmailMessage.FlagStatus = olNoFlag Debug.Print " ...moving" objEmailMessage.Move objFolder Debug.Print " ...finding" objEmailMessage = Outlook.Application.GetNamespace("MAPI").GetItemFromID(strEntryID) Debug.Print " ...flagging" objEmailMessage.FlagStatus = olFlagComplete Debug.Print " ...saving" objEmailMessage.Save Debug.Print " ...done" Else Debug.Print " ...flag is ok" objEmailMessage.Move objFolder End If End If '- ' Free memory and exit '- Finish: Set objFolder = Nothing Exit Function '- ' Error handling '- ErrorHandler: If Err.Description = "Can't move the items." Then Debug.Print " ...failed to move" Else Call HandleError(Err.Number, Err.Description) End If Resume Finish End Function
|
Please note that the debug code is there just to highlight where the error is occurring. I thought that I would be able to track the EntryID of the MailItem and use it to find the email again after the move, but it seems that the EntryID must change due to the move. I don't know of any other reliable way to find the email again.
So, if I cannot find the email after moving, then I am stuck... obviously the reflagging of the email has to be done after moving and not before, because that's how this whole problem started - not being able to move a flagged mail item.
If anyone can offer any advice on this subject then I will be very grateful. I have found one similar posting on these forums, but the fix there was to save before moving, which obviously does not apply here.
[4358 byte] By [
krycek] at [2007-12-16]
Hi Paul
Thanks for the link; I have now posted there as well.
However, I am a little confused - you appear to be telling me that I posted in the wrong place, but I'm sure I did not. The issue that I posted about is to do with programming Outlook with VB/VBA and that is surely appropriate? It doesn't matter that this instance of the code happens to be using VBA - you will get exactly the same issue when using VB.
So, I am still looking for an answer to this issue...
By way of update, I discovered that MailItem.Move actually returns a new instance of the MailItem object, which enables me to reference it after the move. So that's ok. However, when I try to flag the moved item as complete, I get the error message that started it all - about being unable to flag unsent items.
So my method is now:
- unflag
- save
- move
- reflag
- save
...except it fails on the reflag step. So, no matter if I try to simply move the completed item, or unflag, move, and reflag it, I am back to the same error.
Can someone please shed some light on this for me?
Ok, I finally found out the problem, no thanks to this forum or the newsgroup, either. I have been quite surprised at that.
Anyway, here's the solution for anyone else who might have this issue.
When the MailItem is moved, it is changed in more ways than one. It is important to reassign the object from the result of MailItem.Move, in order to preserve a means of referencing the object. This is because the EntryID changes. However, the newly moved object for some reason has the MailItem.Sent property set to false, now.
Because this property is read-only, we initially appear to be stumped. We have a means of accessing the newly moved object, but we cannot re-flag it as complete, because it is unsent... and we cannot mark it as sent, because that property is read-only.
However, I found that quite bizarrely, the status of MailItem.Sent does not seem to be persistant. I checked the status before and after moving, and it was always True before and False after. This led me to try a slightly different approach.
By using the result of MailItem.Send to access the moved MailItem, we can then access the EntryID property. And here's the crux of the solution - if we then get the object again, from GetItemFromID(), then the MailItem.Sent property will be True once more!
So, the process is:
- unflag
- move
- find
- flag
- save
Here is the function, working, with some debug code in to make it clearer what is happening:
| |
Function MoveEmailItemToFolder(objEmailMessage As MailItem, strFolder As String) '- ' Description '- ' This function moves the supplied email message to the specified folder. On Error GoTo ErrorHandler '- ' Declarations '- Dim objFolder As MAPIFolder ' Destination folder '- ' Initialisation '- Set objFolder = Outlook.Application.GetNamespace("MAPI").GetFolderFromID(colFolders.Item(strFolder)) '- ' Code '- If objFolder Is Nothing Then MsgBox ("The specified folder (below) is invalid and could not be referenced" _ & Chr(13) & Chr(13) & strFolder) Else Debug.Print " ...moving" If objEmailMessage.FlagStatus = olFlagComplete Then If objEmailMessage.Sent Then Debug.Print " ...SENT" Else Debug.Print " ...UNSENT" End If Debug.Print " ...unflagging" objEmailMessage.FlagStatus = olNoFlag Debug.Print " ...moving" Set objEmailMessage = objEmailMessage.Move(objFolder) If objEmailMessage.Sent Then Debug.Print " ...SENT" Else Debug.Print " ...UNSENT" End If Debug.Print " ...finding" Set objEmailMessage = Outlook.Application.GetNamespace("MAPI").GetItemFromID(objEmailMessage.EntryID) If objEmailMessage.Sent Then Debug.Print " ...SENT" Else Debug.Print " ...UNSENT" End If Debug.Print " ...flagging" 'objEmailMessage.Sent = True objEmailMessage.FlagStatus = olFlagComplete Debug.Print " ...saving" objEmailMessage.Save Debug.Print " ...done" Else Debug.Print " ...flag is ok" Set objEmailMessage = objEmailMessage.Move(objFolder) End If End If '- ' Free memory and exit '- Finish: Set objFolder = Nothing Exit Function '- ' Error handling '- ErrorHandler: If Err.Description = "Can't move the items." Then Debug.Print " ...failed to move" Else Call HandleError(Err.Number, Err.Description) End If Resume Finish End Function |
...and here's the proper, final function, working, without the debug code:
| |
Function MoveEmailItemToFolder(objEmailMessage As MailItem, strFolder As String) '- ' Description '- ' This function moves the supplied email message to the specified folder. On Error GoTo ErrorHandler '- ' Declarations '- Dim objFolder As MAPIFolder ' Destination folder '- ' Initialisation '- Set objFolder = Outlook.Application.GetNamespace("MAPI").GetFolderFromID(colFolders.Item(strFolder)) '- ' Code '- If objFolder Is Nothing Then MsgBox ("The specified folder (below) is invalid and could not be referenced" _ & Chr(13) & Chr(13) & strFolder) Else Debug.Print " ...moving" If objEmailMessage.FlagStatus = olFlagComplete Then objEmailMessage.FlagStatus = olNoFlag Set objEmailMessage = objEmailMessage.Move(objFolder) Set objEmailMessage = Outlook.Application.GetNamespace("MAPI").GetItemFromID(objEmailMessage.EntryID) objEmailMessage.FlagStatus = olFlagComplete objEmailMessage.Save Else Set objEmailMessage = objEmailMessage.Move(objFolder) End If End If '- ' Free memory and exit '- Finish: Set objFolder = Nothing Exit Function '- ' Error handling '- ErrorHandler: If Err.Description = "Can't move the items." Then Debug.Print " ...failed to move" Else Call HandleError(Err.Number, Err.Description) End If Resume Finish End Function |
A point to note: the colFolders variable is a global collection that stores the MAPI EntryIDs for the folders I am using. Anyone wishing to use this function needs to implement their own method for determining the folder (plenty of ways to do that).
I'm very annoyed that Outlook has this bug but at least I have managed to find a solution. I hope this helps someone else so that they won't waste as much time on this as I have.
Finally, here's an improved version of the function. It now checks if the destination folder is the same as the current folder. A little more sensible, I think.
| |
Function MoveEmailItemToFolder(objEmailMessage As MailItem, strFolder As String) '- ' Description '- ' This function moves the supplied email message to the specified folder. On Error GoTo ErrorHandler '- ' Declarations '- Dim objFolder As MAPIFolder ' Destination folder '- ' Initialisation '- Set objFolder = Outlook.Application.GetNamespace("MAPI").GetFolderFromID(colFolders.Item(strFolder)) '- ' Code '- If objEmailMessage.Parent.EntryID = colFolders.Item(strFolder) Then GoTo Finish End If If objFolder Is Nothing Then MsgBox ("The specified folder (below) is invalid and could not be referenced" _ & Chr(13) & Chr(13) & strFolder) Else Debug.Print " ...moving" If objEmailMessage.FlagStatus = olFlagComplete Then objEmailMessage.FlagStatus = olNoFlag Set objEmailMessage = objEmailMessage.Move(objFolder) Set objEmailMessage = Outlook.Application.GetNamespace("MAPI").GetItemFromID(objEmailMessage.EntryID) objEmailMessage.FlagStatus = olFlagComplete objEmailMessage.Save Else Set objEmailMessage = objEmailMessage.Move(objFolder) End If End If '- ' Free memory and exit '- Finish: Set objFolder = Nothing Exit Function '- ' Error handling '- ErrorHandler: Call HandleError(Err.Number, Err.Description) Resume Finish End Function |
Enjoy.
Thanks a lot for this. I have been banging my head against the wall over this one for a while. Good to see good people doing good things...
Also wanted to say thanks...this has been driving me crazy for the last week or so. This, however, works like a charm.
Hi Krycek,
Technically this forum is for Visual Studio Tools for Office which is managed code development, there are people on this forum from MS and MVP's and other technical people like myself, who have expieriences and as such may have helped but there are more specific areas that may and are more suitable for Outlook VBA not Outlook VSTO. This forum is for Managed Code but we may have assisted if we had chance, it is important that we focus on the correct product groups to make sure it is targetted correctly.
Ironically I have had expierience with this and could have highlighted some of these areas but only just saw the original post today
- Outlook has a lot of quirks and is very annoying at times in its inability to be consistent.
Regards
Sorry if there's an etiquette violation for replying to a thread this old...
I would like to thank krycek for this gem, and also point out to others that the trick of creating an object and then reassigning it based on its EntryID works well for MailItem.Copy as well as move. When you copy a sent-mail item, for example, the new item has the current date as the received date and has the sent property = false. There may be other properties that are changed. If you immediately reassign it as above, these properties will have their original values and you can proceed with a better copy than you had originally.