Delete draft mail on close when not sent - vba

I've got some draft mails with some buttons to copy and open them. Only a few values need to be filled in and then the mails will be sent. I want to keep the drafts. But if a mail is not sent, I would like to delete it because it is a copy. I'm working with the close event for a mail item, but I can't seem to find out how to delete it in that sub, tried many things. Anyone knows how to approach this?
Code I got so far in a module:
Dim itmevt As New CMailItemEvents
Public olMail As Variant
Public olApp As Outlook.Application
Public olNs As NameSpace
Public Fldr As MAPIFolder
Sub TeamcenterWEBAccount()
Dim i As Integer
Dim olMail As Outlook.MailItem
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set Fldr = olNs.GetDefaultFolder(olFolderDrafts)
For Each olMail In Fldr.Items
If InStr(olMail.Subject, "New account") <> 0 Then
Set NewItem = olMail.Copy
olMail.Display
Set itmevt.itm = olMail
Exit Sub
End If
Next olMail
End Sub
Code in the CMailItemEvents class module:
Option Explicit
Public WithEvents itm As Outlook.MailItem
Private Sub itm_Close(Cancel As Boolean)
Dim blnSent As Boolean
On Error Resume Next
blnSent = itm.Sent
If blnSent = False Then
itm.DeleteAfterSubmit = True
Else
' do
End Sub

Please, try the next way:
Copy the next adapted code (instead of your code, or in a new standard module):
Option Explicit
Private itmevt As New CMailItemEvents
Public deleteFromDrafts As Boolean, boolContinue As Boolean
Sub TeamcenterWEBAccount()
Dim olMail As Outlook.MailItem, NewItem As Outlook.MailItem, boolDisplay As Boolean
Dim olApp As Outlook.Application, Fldr As MAPIFolder, olNs As NameSpace
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set Fldr = olNs.GetDefaultFolder(olFolderDrafts)
For Each olMail In Fldr.Items
If InStr(olMail.Subject, "New account") > 0 Then
On Error Resume Next 'for the case of inline response
Set NewItem = olMail.Copy
If Err.Number = -2147467259 Then
Err.Clear: On Error GoTo 0
olMail.Display: boolDisplay = True
For i = 1 To 1000: DoEvents: Next i 'just wait for the window to be displayed...
Set NewItem = olMail.Copy
End If
On Error GoTo 0
deleteFromDrafts = False: boolContinue = False 'initialize the boolean variables to wait for them in the loop
If Not boolDisplay Then olMail.Display
Set itmevt.itm = olMail
'wait for close event to be triggered...
Do While deleteFromDrafts = False And boolContinue = False
DoEvents
Loop
If deleteFromDrafts Then
If Not olMail Is Nothing Then olMail.Delete 'let only the copy...
End If
Exit Sub
End If
Next olMail
End Sub
Copy the next adapted code to replace the existing one in the used class:
Option Explicit
Public WithEvents itm As Outlook.MailItem
Private Sub itm_Close(Cancel As Boolean)
Dim blnSent As Boolean
On Error GoTo Ending 'for the case of mail sending, when itm looses its reference...
If blnSent = False Then
itm.DeleteAfterSubmit = True
deleteFromDrafts = True
Else
boolContinue = True
End If
Exit Sub
Ending:
boolContinue = True
End Sub
Tested, but not intensively...
Please, send some feedback after testing it in your specific environment.

First of all, iterating over all items in the folder is not really a good idea:
For Each olMail In Fldr.Items
If InStr(olMail.Subject, "New account") <> 0 Then
Instead, let the store provider do the job for you. The Find/FindNext or Restrict methods of the Items class allows getting items that correspond to your conditions, so you could iterate over items needed. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
You may try handling the Close event of the Inspector class which is fired when the inspector associated with a Microsoft Outlook item is being closed.
But I think none of them can be helpful. You need to re-design the whole solution by tracking for new items in the folder. And if new items have a custom property which indicates whether to remove the item or not you can do the additional actions. In the item-level event it is impossible to delete the source item.

My changes in module:
Private itmevt As New CMailItemEvents
Public deleteFromDrafts As Boolean, boolContinue As Boolean, boolDisplay As Boolean
Private olMail As Outlook.MailItem, NewItem As Outlook.MailItem
Private olApp As Outlook.Application, olNs As NameSpace, Fldr As MAPIFolder
Sub TeamcenterWEBAccount()
AccountOrInstallation ("Nieuw TC11 VDL ETG Teamcenter WEB account")
End Sub
Sub AccountOrInstallation(ByVal SearchStr As String)
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set Fldr = olNs.GetDefaultFolder(olFolderDrafts)
For Each olMail In Fldr.Items
If InStr(olMail.Subject, SearchStr) > 0 Then
On Error Resume Next 'for the case of inline response
Set NewItem = olMail.Copy
If Err.Number = -2147467259 Then
Err.Clear: On Error GoTo 0
olMail.Display: boolDisplay = True
For i = 1 To 1000: DoEvents: Next i 'just wait for the window to be displayed...
Set NewItem = olMail.Copy
End If
On Error GoTo 0
deleteFromDrafts = False: boolContinue = False 'initialize the boolean variables to wait for them in the loop
If Not boolDisplay Then olMail.Display
Set itmevt.itm = olMail
'wait for close event to be triggered...
Do While deleteFromDrafts = False And boolContinue = False
DoEvents
Loop
If deleteFromDrafts Then
If Not olMail Is Nothing Then olMail.Delete 'let only the copy...
End If
Exit Sub
End If
Next olMail
End Sub

Related

Move Email with ItemAdd

I'm attempting to move email with specific subject when received.
The mail is still in my Inbox. I've tested by sending mail from my account with specific subject.
Option Explicit
Private WithEvents inboxItems As Outlook.Items
Private Sub Application_Startup()
Dim outlookApp As Outlook.Application
Dim objectNS As Outlook.NameSpace
Set outlookApp = Outlook.Application
Set objectNS = outlookApp.GetNamespace("MAPI")
Set inboxItems = objectNS.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub Items_ItemAdd(ByVal item As Object)
On Error GoTo ErrorHandler
Dim Msg As Outlook.MailItem
Set myInbox = GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
If TypeName(item) = "MailItem" Then
Set Msg = item
If InStr(0, Msg.Subject, "Testing Subject", vbTextCompare) > 0 Then
Set fldr = Outlook.Session.GetDefaultFolder(olFolderInbox).Folders("Testing")
Msg.Move fldr
End If
End If
ProgramExit:
Exit Sub
ErrorHandler:
MsgBox Err.Number & " - " & Err.Description
Resume ProgramExit
End Sub
I created this macro in ThisOutlookSession.
I think you may have an error with the naming of your Sub which means it doesn't fire
Items_ItemAdd => inboxItems_ItemAdd
As an addendum: I recently implemented a RegEx filter to incoming e-mails as I found I couldn't easily use rules to filter out some junk e-mail coming my way. This should be able to adapted to your needs (I've added the rule I think should work for you, but it's untested)
Within the 'ThisOutlookSession'
Private Sub Application_NewMailEx(ByVal EntryIDCollection As String)
On Error Resume Next
Call RegExFilterRules(EntryIDCollection)
End Sub
Within a module
Sub RegExFilterRules(ItemID As String)
' Requires Reference: Microsoft Scripting Runtime
Dim ThisNamespace As Outlook.NameSpace: Set ThisNamespace = Application.GetNamespace("MAPI")
Dim Inbox As Outlook.MAPIFolder: Set Inbox = ThisNamespace.GetDefaultFolder(olFolderInbox)
Dim Junk As Outlook.MAPIFolder: Set Junk = ThisNamespace.GetDefaultFolder(olFolderJunk)
Dim oMsg As Outlook.MailItem: Set oMsg = ThisNamespace.GetItemFromID(ItemID, Inbox.StoreID)
If Not oMsg Is Nothing And oMsg.Class = olMail Then
'If FindPattern(oMsg.Subject, "^M\d+$") Then oMsg.Move Junk ' oMsg.Delete
If FindPattern(oMsg.Subject, "^Testing Subject") Then oMsg.Move Inbox.Folders("Testing")
End If
End Sub
Private Function FindPattern(Str As String, Pattern As String) As Boolean
Dim RegEx As Object: Set RegEx = CreateObject("vbscript.RegExp")
With RegEx
.Global = True
.IgnoreCase = True
.MultiLine = True
.Pattern = Pattern
FindPattern = .Test(Str)
End With
End Function

Copy method in ItemAdd generates Runtime Error

When I run this code I get the error:
Run-Time error '-2147221233 (8004010f)':
The attempted operation failed. An object could not be found.
Everything is working despite the error.
The error disappears if I change the line
'MsgBox "Awesome"
to
MsgBox "Awesome"
A few tests showed that the error does occur if item.Sendername is used with the copy part. If I do just move the mail it works perfectly.
If I try to use the code separately it works without errors.
Private WithEvents snItems As Items
Private Sub Application_Startup()
Set snItems = Session.GetDefaultFolder(olFolderSentMail).Items
End Sub
Private Sub snItems_ItemAdd(ByVal item As Object)
Dim CopiedItem As MailItem
Dim ShareInbox As Outlook.MAPIFolder
Dim MapiNameSpace As Outlook.NameSpace
If TypeName(item) = "MailItem" Then
Set MapiNameSpace = Application.GetNamespace("MAPI")
Set ShareInbox = MapiNameSpace.Folders("Support").Folders("Send Mails")
If item.SenderName = "Support" Then
Set CopiedItem = item.Copy
CopiedItem.UnRead = True
CopiedItem.Move ShareInbox
End If
End If
'MsgBox "Awesome"
ExitRoutine:
Set ShareInbox = Nothing
Set CopiedItem = Nothing
Set MapiNameSpace = Nothing
End Sub
There is no error if not copied.
It is ok with the following Code
Set MapiNameSpace = Application.GetNamespace("MAPI")
Set ShareInbox = MapiNameSpace.Folders("Support").Folders("Gesendete Elemente")
If item.SenderName = "Support" Then
item.Move ShareInbox
End If
Copying the item adds an item to the Sent Items folder, triggering the ItemAdd code.
Disable the ItemAdd event temporarily.
Private Sub snItems_ItemAdd(ByVal item As Object)
Dim CopiedItem As MailItem
Dim ShareInbox As Outlook.MAPIFolder
Dim MapiNameSpace As Outlook.NameSpace
If TypeName(item) = "MailItem" Then
Set MapiNameSpace = Application.GetNamespace("MAPI")
Set ShareInbox = MapiNameSpace.Folders("Support").Folders("Send Mails")
If item.SenderName = "Support" Then
' Turn off event handling
Set snItems = Nothing
Set CopiedItem = item.Copy
CopiedItem.UnRead = True
CopiedItem.Move ShareInbox
' Turn on event handling
Set snItems = Session.GetDefaultFolder(olFolderSentMail).Items
End If
End If
ExitRoutine:
Set ShareInbox = Nothing
Set CopiedItem = Nothing
Set MapiNameSpace = Nothing
End Sub

How would I cycle through an open Outlook windows using VBA?

I often have multiple email items open, some that I composed that are not yet sent and others that I received that I didn't yet close but I am referencing.
What I would like to do is have a fast way to cycle through all of the open windows to find the one that I am looking for.
In Excel I created a macro to cycle through the tabs of an Excel document like this.
Sub PreviousSheet()
On Error Resume Next
ActiveSheet.Previous.Select
End Sub
Sub NextSheet()
On Error Resume Next
ActiveSheet.Next.Select
End Sub
In Outlook, how would I cycle through the open windows using VBA?
Update
Sub test()
Dim olApp As Outlook.Application
Set olApp = GetOutlookApp()
'I think this is how to loop through the open items?
For i = olApp.Inspectors.Count To 1 Step -1
Set olItem = olApp.Inspectors.Item(i).CurrentItem
olItem.Select 'How do I set focus?
Next i
End Sub
Function GetOutlookApp() As Outlook.Application
' returns reference to native Application object
Set GetOutlookApp = Outlook.Application
End Function
Sub GetPreviousOpenItem()
Set MainWindow = Application.ActiveExplorer
Dim olApp As Outlook.Application
Set olApp = GetOutlookApp()
If olApp.ActiveInspector Is Nothing Then
Exit Sub
End If
ActiveInspectorIndex = GetIndexOfActiveInspector(olApp, olApp.ActiveInspector)
If ActiveInspectorIndex - 1 > 0 Then
Dim PreviousInspector As Inspector
Set PreviousInspector = olApp.Inspectors(ActiveInspectorIndex - 1)
olApp.Inspectors(ActiveInspectorIndex - 1).Display
Else
olApp.Inspectors(olApp.Inspectors.Count).Display
End If
MainWindow.Activate
End Sub
Sub GetNextOpenItem()
Set MainWindow = Application.ActiveExplorer
Dim olApp As Outlook.Application
Set olApp = GetOutlookApp()
If olApp.ActiveInspector Is Nothing Then
Exit Sub
End If
ActiveInspectorIndex = GetIndexOfActiveInspector(olApp, olApp.ActiveInspector)
If ActiveInspectorIndex + 1 <= olApp.Inspectors.Count Then
Dim NextInspector As Inspector
Set NextInspector = olApp.Inspectors(ActiveInspectorIndex + 1)
NextInspector.Display
Else
olApp.Inspectors(1).Display
End If
MainWindow.Activate
End Sub
Function GetIndexOfActiveInspector(olApp, CurrentItem) As Integer
CurrentItem = olApp.ActiveInspector
For i = 1 To olApp.Inspectors.Count
Dim Inspector
Set Inspector = olApp.Inspectors.Item(i)
Set olItem = Inspector.CurrentItem
If olItem Is CurrentItem Then
GetIndexOfActiveInspector = i
Exit Function
End If
Next i
MainWindow.Activate
End Function
Function GetOutlookApp() As Outlook.Application
' returns reference to native Application object
Set GetOutlookApp = Outlook.Application
End Function

Outlook VBA to save sent meeting requests/replies to a specified folder

I found the code below (here) to get outlook VBA to save sent email to a specified folder.
The code works well, however, any time that it runs on a sent meeting request or meeting reply, it errors.
I have been able to identify line 9 as the line where the error occurs:
Set Item.SaveSentMessageFolder = objFolder
My assumption, then, is that the Item.SaveSentMessageFolder code is incompatible with meeting-type objects. However, I am uncertain as to what the equivalent coding would be for meeting-type objects.
Can this code be modified to handle meeting-type objects in the same fashion that message-type objects are handled?
Private Sub Application_ItemSend(ByVal Item As Object, _
Cancel As Boolean)
Dim objNS As NameSpace
Dim objFolder As MAPIFolder
Set objNS = Application.GetNamespace("MAPI")
Set objFolder = objNS.PickFolder
If TypeName(objFolder) <> "Nothing" And _
IsInDefaultStore(objFolder) Then
Set Item.SaveSentMessageFolder = objFolder
End If
Set objFolder = Nothing
Set objNS = Nothing
End Sub
Public Function IsInDefaultStore(objOL As Object) As Boolean
Dim objApp As Outlook.Application
Dim objNS As Outlook.NameSpace
Dim objInbox As Outlook.MAPIFolder
On Error Resume Next
Set objApp = CreateObject("Outlook.Application")
Set objNS = objApp.GetNamespace("MAPI")
Set objInbox = objNS.GetDefaultFolder(olFolderInbox)
Select Case objOL.Class
Case olFolder
If objOL.StoreID = objInbox.StoreID Then
IsInDefaultStore = True
End If
Case olAppointment, olContact, olDistributionList, _
olJournal, olMail, olNote, olPost, olTask
If objOL.Parent.StoreID = objInbox.StoreID Then
IsInDefaultStore = True
End If
Case Else
MsgBox "This function isn't designed to work " & _
"with " & TypeName(objOL) & _
" items and will return False.", _
, "IsInDefaultStore"
End Select
Set objApp = Nothing
Set objNS = Nothing
Set objInbox = Nothing
End Function
At least one other person has found the property to be ineffective for meeting items.
https://msdn.microsoft.com/en-us/vba/outlook-vba/articles/meetingitem-savesentmessagefolder-property-outlook
"Setting or getting this property has no noticeable effect. Do not use this property."
Try monitoring the Sent Items folder.
Place this code in the ThisOutlookSession module.
Private WithEvents sentMailItems As items
Private Sub Application_Startup()
Set sentMailItems = Session.GetDefaultFolder(olFolderSentMail).items
End Sub
Private Sub sentMailItems_ItemAdd(ByVal Item As Object)
Dim objFolder As Folder
If TypeOf Item Is MeetingItem Then
Set objFolder = Session.PickFolder
If TypeName(objFolder) <> "Nothing" Then
Item.Move objFolder
End If
End If
Set objFolder = Nothing
End Sub

Mark as Read loop errors on some mail items inconsistently

I receive 4000+ emails over a weekend and all throughout the week that are filtered into folders via rules. I created a macro to mark all these folders as read. However, on some mail items it errors with runtime error 91 object variable or with block variable not set.
If I skip the errors with On Error Resume Next it loops through everything but just doesn't set a bunch of the mail items as read. I can then rerurn the macro to get most of the remaining ones. If I run the macro 3-4 times it will eventually get them all.
How can I improve this macro to consistently mark ALL the items as read?
Public Function GetInboxFolderID(FolderName As String) As String
Dim nsp As Outlook.Folder
Dim mpfSubFolder As Outlook.Folder
Dim mpfSubFolder2 As Outlook.Folder
Dim flds As Outlook.Folders
Dim flds2 As Outlook.Folders
Set nsp = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
Set flds = nsp.Folders
Set mpfSubFolder = flds.GetFirst
Do While Not mpfSubFolder Is Nothing
If mpfSubFolder.Name = FolderName Then
GetInboxFolderID = mpfSubFolder.EntryID
Exit Function
End If
Set flds2 = mpfSubFolder.Folders
Set mpfSubFolder2 = flds2.GetFirst
Do While Not mpfSubFolder2 Is Nothing
If mpfSubFolder2.Name = FolderName Then
GetInboxFolderID = mpfSubFolder2.EntryID
Exit Function
End If
Set mpfSubFolder2 = flds2.GetNext
Loop
Set mpfSubFolder = flds.GetNext
Loop
End Function
Private Sub processFolder(ByVal oParent As Outlook.MAPIFolder)
Dim oFolder As Outlook.MAPIFolder
Dim oMail As Outlook.MailItem
Dim oFiltered As Outlook.Items
Set oFiltered = oParent.Items.Restrict("[unread] = true")
On Error Resume Next
For Each oMail In oFiltered
oMail.UnRead = False
Next
If (oParent.Folders.Count > 0) Then
For Each oFolder In oParent.Folders
processFolder oFolder
Next
End If
End Sub
Public Sub markNocAsRead()
Dim SubFolder As MAPIFolder
Set SubFolder = Session.GetDefaultFolder(olFolderCalendar).Parent.Folders("NOC Alerts")
'Application.Session.GetFolderFromID (GetInboxFolderID("NOC Alerts"))
Call processFolder(SubFolder)
End Sub
I was able to get this working consistently by taking the advice of Ryan Wildry in a comment above.
I replaced my loop:
Set oFiltered = oParent.Items.Restrict("[unread] = true")
On Error Resume Next
For Each oMail In oFiltered
oMail.UnRead = False
Next
with a loop that iterates from the end to beginning:
Set oFiltered = oParent.Items.Restrict("[unread] = true")
For I = oFiltered.Count To 1 Step -1
Set oMail = oFiltered(I)
oMail.UnRead = False
Next