Outlook Macro to Move entire conversations in a thread - vba

I have written a macro which should move my email conversation to my "TO DO" folder whenever I flag the email as important. I find that the move function does happen, but I get a copy (i.e. the thread shows in both my "TODO"folder and still remains in the "Inbox".
What is also interesting is that in this line of code "For Each MailItem In Conversation.GetRootItems" I would have expected since all the messages do get moved that MailItem>1, but in fact that bit of code only executes one time and then the loop completes. Any thoughts on how to do a true move as opposed to be what appears to be a copy?
''''
Public WithEvents GExplorer As Outlook.Explorer
Public WithEvents GMailItem As Outlook.MailItem
Private WithEvents Items As Outlook.Items
Private Sub Application_Startup()
Set GExplorer = Outlook.Application.ActiveExplorer 'IGNORE THIS'
Dim olNameSpace As Outlook.NameSpace
Dim olFolder As Outlook.MAPIFolder
Set olNameSpace = Application.GetNamespace("MAPI")
Set olFolder = olNameSpace.GetDefaultFolder(olFolderInbox)
Set Items = olFolder.Items
End Sub
Private Sub Items_ItemChange(ByVal Item As Object)
'this item/macro is used to move an email message once it has been flagged
Dim olNameSpace As Outlook.NameSpace
Dim olFolder As Outlook.MAPIFolder
Dim olInbox As Outlook.MAPIFolder
Set olNameSpace = Application.GetNamespace("MAPI")
Set olFolder = olNameSpace.Folders("DEBUG").Folders("TODO")
Stop 'THIS WAS FOR DEBUGGING
If TypeOf Item Is Outlook.MailItem And Item.FlagStatus = olFlagMarked Then
Set Conversation = Item.GetConversation
If Not IsNull(Conversation) Then
' Set ItemsTable = conversation.GetTable
'MsgBox Conversation.GetRootItems.Count
For Each MailItem In Conversation.GetRootItems ' Items in the conversation. ONLY RUNS ONCE'
If TypeOf MailItem Is Outlook.MailItem Then
Item.Move olFolder
End If
Next
End If
End If
End Sub
''''

That is because the same action (move) is repeated for the item changed and passed as a parameter to the ItemChange event handler. Instead, you must run the Move method against the item object in the loop:
For Each MailItem In Conversation.GetRootItems ' Items in the conversation. ONLY RUNS ONCE'
If TypeOf MailItem Is Outlook.MailItem Then
MailItem.Move olFolder
End If
Next

In addition to Eugene's suggestion (use MailItem instead of item when calling Move), you should never use "for each" with Outlook objects in loops that modify the collection you are iterating over - use a down loop from Count to 1 step -1.

Maybe you need to GetChildren of the conversation.

Related

Move mails to folders with the sender's name

It is possible to create a rule which, for a sender, moves all the mails to the folder of your choice (for example, it creates a folder with the name of the sender).
If I want that for all the expeditors, I need to repeat the rule creation for each sender.
What I'd wish would be a macro "meta-rule" for each sender to have a folder with their name with the corresponding mails sorted.
I tried to start from the topic Outlook template rule to sort mails among directories.
I wrote this:
Sub RulesForFolders(m As MailItem)
Dim fldr As Outlook.Folder
For Each fldr In GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Folders
if fldr.Name Like m.SenderName Then m.MoveTo(SenderName)
else folders.add(m.SenderName)
Next
Set fldr = Nothing
End Sub
Option Explicit ' Consider this mandatory
' Tools | Options | Editor tab
' Require Variable Declaration
'
' If desperate declare as variant
Private Sub RulesForFolders(m As mailItem)
Dim targetFldr As folder
Dim myRoot As folder
Dim i As Long
Set myRoot = Session.GetDefaultFolder(olFolderInbox)
Debug.Print m.senderName
' This is often misused.
On Error Resume Next
' If folder exists the error is bypassed
' This is a rare beneficial use of On Error Resume Next
myRoot.folders.Add m.senderName
' Consider it mandatory to return to normal error handling
On Error GoTo 0
Set targetFldr = myRoot.folders(m.senderName)
m.Move targetFldr
End Sub
Private Sub RulesForFolders_test()
' Code requiring a parameter cannot run independently
Dim selItem As Object
' first select a mailitem
Set selItem = ActiveExplorer.Selection(1)
If selItem.Class = olMail Then
RulesForFolders ActiveExplorer.Selection(1)
End If
End Sub
First of all, I'd suggest starting from the NewMailEx event of the Application class which is fired when a new item is received in the Inbox. This event fires once for every received item that is processed by Microsoft Outlook. The item can be one of several different item types, for example, MailItem, MeetingItem, or SharingItem. The EntryIDsCollection string contains the Entry ID that corresponds to that item. The NewMailEx event fires when a new message arrives in the Inbox and before client rule processing occurs. You can use the Entry ID returned in the EntryIDCollection array to call the NameSpace.GetItemFromID method and process the item.
To find the folder with a sender name you can iterate over all subfolders recursively:
Private Sub processFolder(ByVal oParent As Outlook.MAPIFolder)
Dim oFolder As Outlook.MAPIFolder
Dim oMail As Outlook.MailItem
For Each oMail In oParent.Items
'Get your data here ...
Next
If (oParent.Folders.Count > 0) Then
For Each oFolder In oParent.Folders
processFolder oFolder
Next
End If
End Sub
Finally, I'd recommend delving deeper with VBA by starting from the Getting started with VBA in Office article.
You can also use the following code if you don't need to iterate over all folders:
Sub RulesForFolders(m As MailItem)
Dim fldr As Outlook.Folder
Dim new_fldr As Outlook.Folder
Dim ns as Outlook.Namespace
Dim inbox as Outlook.Folder
Set ns = Application.GetNamespace("MAPI")
Set inbox = ns.GetDefaultFolder(olFolderInbox)
For Each fldr In inbox.Folders
if InStr(fldr.Name,m.SenderName) > 0 Then
m.MoveTo(fldr)
Return
End If
Next
Set new_fldr = folders.add(m.SenderName)
m.MoveTo(new_fldr)
Set fldr = Nothing
Set new_fldr = Nothing
Set inbox = Nothing
Set ns = Nothing
End Sub

oultlook vba loop over inbox, not bulk scan

We have a shared mailbox, and the alerts folder gets filled with alerts. Thousands of them - most from start of day and end of day. New alerts in the middle of the day might actually be something that we need look at.
Noone ever bulk marks the folder as read in the morning, because it takes too long - you can't highlight the who mailbox and click "mark as unread". The onely way to mark the emails is by highlighting a few hundred at a time - which takes along time manually.
I made thsi script because it will automagically mark the emails in the "alerts' folder. However, it seems to go after the whole folder at the same time. The script ist eh equivalent of highlightimg the whole folder, and marking a bulk delete. It takes to long and locks up the shared email box. I would like something that would start at the bottom of the folder, cycle through each email, and if unread, mark the email unread. pause for a second, then the next one.
Is that possible?
Sub Test2()
Dim objInbox As Outlook.MAPIFolder
Dim objOutlook As Object, objnSpace As Object, objMessage As Object
Dim objSubfolder As Outlook.MAPIFolder
Set objOutlook = CreateObject("Outlook.Application")
Set objnSpace = objOutlook.GetNamespace("MAPI")
Set objInbox = objnSpace.GetDefaultFolder(olFolderInbox)
Set objSubfolder = objInbox.Folders.Item("_ALERTS")
For Each objMessage In objSubfolder.Items
objMessage.UnRead = False
Next
Set objOutlook = Nothing
Set objnSpace = Nothing
Set objInbox = Nothing
Set objSubfolder = Nothing
End Sub
You can create a code in Outlook which get triggers when new Email entered in a target folder.
Public WithEvents objMails As Outlook.Items
Private Sub Application_Startup()
Set objMails = Outlook.Application.Session.GetDefaultFolder(olFolderInbox).Folders("_ALERTS").Items
End Sub
Private Sub objMails_ItemAdd(ByVal Item As Object)
'Do more stuff
End Sub
which will avoid looping through all the emails at a time
You could use Restrict to limit the items to be processed.
Option Explicit
Sub Test2()
Dim objInbox As Folder
Dim objnSpace As namespace
Dim objSubfolder As Folder
dim unreadItems As items
dim unreaditemsCount as long
Set objnSpace = GetNamespace("MAPI")
Set objInbox = objnSpace.GetDefaultFolder(olFolderInbox)
Set objSubfolder = objInbox.Folders.Item("_ALERTS")
set unreadItems = objSubfolder.Items.Restrict("[UnRead] = True")
unreaditemsCount = unreadItems.Count
If unreaditemsCount > 0 Then
' Reverse loop when changing the number of items in the collection
For i = unreaditemsCount to 1
unreadItems(i).UnRead = False
Next
end if
Set objInbox = Nothing
Set objnSpace = Nothing
Set objSubfolder = Nothing
Set unreadItems = Nothing
End Sub

Runtime error '13' type mismatch

I created a macro to download attachments form the outlook whenever we receive a new mail, but I am getting error as "Run time Error '13' Type mismatch " and below is the code am using.
Can anyone please help me in resolving this.
Option Explicit
Private Sub Application_NewMail()
Dim onamespace As Outlook.NameSpace
Set onamespace = Outlook.GetNamespace("MAPI")
Dim myfol As Outlook.Folder
Set myfol = onamespace.GetDefaultFolder(olFolderInbox)
Dim omail As Outlook.MailItem
Set omail = Outlook.CreateItem(olMailItem)
Dim Atmt As Outlook.Attachment
For Each omail In myfol.Items
If omail.SenderEmailAddress = "sacchu693#gmail.com" Then
For Each Atmt In omail.Attachments
Atmt.SaveAsFile "Z:\True_ID\46 RSA\" & Atmt.FileName
Next
Else
End If
Next
End Sub
Your code does not make much sense - you are creating a new message (oMail), but you never do anything with it. You just use the variable declaration to loop over all items in the Inbox. Since it is declared as MailItem, it blows up when it encounters an item of a type other than MailItem (such as ReportItem or MeetingItem).
Use NewMailEx event instead - it passes the new message's entry id as the parameter. Use it to call Namespace.GetItemFromID.

How to access the Flag completed date?

I move my emails by selecting "Follow up - Mark completed Option", to another folder.
Now I want to write a program to check hom many emails did I complete today by comparing the Flag completed date with todays date.
But I am not able to find how to access the Flag completed date.
Can you please help.
Thanks,
Alok
The property is Outlook.MailItem.TaskCompletedDate. Try something like:
Sub GetCompletedToday()
Dim olNameSpace As Outlook.NameSpace
Dim olFolder As Outlook.Folder
Dim olMailItem As Outlook.MailItem
Dim CompletedTodayCount As Long
Set olNameSpace = Application.GetNamespace("MAPI")
Set olFolder = olNameSpace.Folders(1).Folders("tester")
For Each olMailItem In olFolder.Items
If olMailItem.TaskCompletedDate = Date Then
CompletedTodayCount = CompletedTodayCount + 1
End If
Next olMailItem
Debug.Print CompletedTodayCount
End Sub
You can access the flags by using the expression.FlagStatus
See this link
Topic: FlagStatus Property
Link: http://msdn.microsoft.com/en-us/library/aa212013%28v=office.11%29.aspx
For example, this will give you the status of all selected emails
OUTLOOK VBA CODE
Option Explicit
Sub Sample()
Dim Messages As Selection
Dim Msg As MailItem
Dim NamSpace As NameSpace
Set NamSpace = Application.GetNamespace("MAPI")
Set Messages = ActiveExplorer.Selection
If Messages.Count = 0 Then Exit Sub
For Each Msg In Messages
Debug.Print Msg.FlagStatus
Next
End Sub
For message(s) with No flags it will give you a 0
for Mark Completed it will give you 1 and
for Other flags it will give you 2
So you can actually use an If statement to check for the .FlagStatus property and the Mail Date to achieve what you want.
HTH

Looping over Outlook mail items in "Sent Items" folder

We're trying to access the Sent Items folder in Outlook 2007 (using Exchange) but the test for TypeOf(i) Is Outlook.MailItem in the below code snippet always returns False.
We know we have the right folder because a test for items.Count returns the correct number of mail items.
Inbox messages are fine. If we change the folder from olFolderSentMail to olFolderInbox the test for TypeOf(i) Is Outlook.MailItem passes and it's quite happy to show us the Subject.
Dim app As Outlook.Application = Nothing
Dim ns As Outlook.NameSpace = Nothing
Dim siFolder As Outlook.Folder = Nothing
Dim items As Outlook.Items = Nothing
app = New Outlook.Application()
ns = app.Session
siFolder = CType(ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail), Outlook.Folder)
items = siFolder.Items
MsgBox(items.Count)
For Each i In items
If TypeOf (i) Is Outlook.MailItem Then
Dim mailitem As Outlook.MailItem
mailitem = CType(i, Outlook.MailItem)
MsgBox(mailitem.Subject)
Else
MsgBox("not a mailitem")
End If
Next
Update
#Rob's answer below, yes, definitely has helped. But I'm still puzzled. The crucial thing #Rob's code is doing is testing for .MessageClass = "IPM.Note". If I include that then the later test for TypeOf x Is MailItem succeeds. If I replace #Rob's test for .MessageClass = "IPM.Note" with If True Then then the same code still executes but the later test for Is MailItem fails. It's as if just testing for the .MessageClass automagically resolves the object into a MailItem.
Furthermore the Sent Items don't contain any meeting requests so the test would seem to be unnecessary anyway.
This should get you going ...
....
Dim oSent As Outlook.MAPIFolder = oNS.GetFolderFromID(gSentEntryID, gSentStoreID)
Dim oItems As Outlook.Items = oSent.Items
For i as Integer = 1 To oItems.Count
'Test to make sure item is a mail item and not a meeting request.
If oItems.Item(i).MessageClass = "IPM.Note" Then
If TypeOf oItems.Item(i) Is Microsoft.Office.Interop.Outlook.MailItem Then
.....