Populate emails from Inbox to a listbox faster way - sql

I have the following scenario. Populate a listbox1 with emails from InBox. I am using the
followings approach (Loop'For Each' and Sql '1 to count'):
LOOP
Sub Example_LOOP()
Dim objNS As Outlook.Namespace: Set objNS = GetNamespace("MAPI")
Dim olFolder As Outlook.MAPIFolder
Dim Item As Object
Dim textDisplay As String
Dim resultReceived As String
Set olFolder = objNS.GetDefaultFolder(olFolderInbox)
For Each Item In olFolder.Items
If TypeOf Item Is Outlook.MailItem Then
Dim oMail As Outlook.MailItem: Set oMail = Item
textDisplay = oMail.Sender & " | " & oMail.Subject & " | " & oMail.ReceivedTime
resultReceived = textDisplay & "§" & resultReceived
End If
Next
With Me.ListBox1
.Clear
.List = Split(resultReceived, "§")
.RemoveItem (Me.ListBox1.ListCount - 1)
End With
End Sub
and SQL:
Public Sub Example_SQL()
Dim objNS As Outlook.Namespace: Set objNS = GetNamespace("MAPI")
Dim olFolder As Outlook.MAPIFolder: Set olFolder = objNS.GetDefaultFolder(olFolderInbox)
Dim Items As Outlook.Items
Dim Item As Object
Dim i As Long
Dim Filter As String
Dim textDisplay As String
Dim resultReceived As String
Filter = "#SQL=" & Chr(34) & "urn:schemas:httpmail:fromname" & _
Chr(34) & "Like ''"
Set Items = olFolder.Items.Restrict(Filter)
Items.Sort "[ReceivedTime]", 1
For i = Items.Count To 1 Step -1
If TypeOf Items(i) Is MailItem Then
Set Item = Items(i)
textDisplay = Item.Sender & " | " & Item.Subject & " | " & Item.ReceivedTime
resultReceived = textDisplay & "§" & resultReceived
End If
Next
With Me.ListBox1
.Clear
.List = Split(resultReceived, "§")
.RemoveItem (Me.ListBox1.ListCount - 1)
End With
End Sub
Reading 10000 emails takes around 45 seconds. It is possibility to do it faster. Or maybe a different approach?
Thank you.

You can use Tables from the Outlook object model where you can specify only properties you are interested in. So, instead of loading in the loop each item from a folder you may iterate over rows in a table which I think can be much faster.
The Folder.GetTable method obtains a Table object that contains items filtered by Filter. By default, TableContents is olUserItems and the returned Table contains only the filtered items that are not hidden. If Filter is a blank string or the Filter parameter is omitted, GetTable returns a Table with rows representing all the items in the Folder.
GetTable returns a Table with the default column set for the folder type of the parent Folder. To modify the default column set, use the Add, Remove, or RemoveAll methods of the Columns collection object. You can use Table.Restrict to apply subsequent filters to a Table that is based on the Folder object. For example:
Sub DemoTable()
'Declarations
Dim Filter As String
Dim oRow As Outlook.Row
Dim oTable As Outlook.Table
Dim oFolder As Outlook.Folder
'Get a Folder object for the Inbox
Set oFolder = Application.Session.GetDefaultFolder(olFolderInbox)
'Define Filter to obtain items last modified after May 1, 2021
Filter = "[LastModificationTime] > '5/1/2021'"
'Restrict with Filter
Set oTable = oFolder.GetTable(Filter)
'Enumerate the table using test for EndOfTable
Do Until (oTable.EndOfTable)
Set oRow = oTable.GetNextRow()
Debug.Print (oRow("Subject"))
Debug.Print (oRow("LastModificationTime"))
Loop
End Sub`

In addition to Eugene's suggestion, do not load all items. Load them on demand as the user scrolls through the list.

I have done it like this, and it is working fine (I can add only the properties that I'm looking for). Thanks to show me the direction. :)
Public Sub ExtractInBoxInfo()
Dim objNS As Outlook.Namespace: Set objNS = GetNamespace("MAPI")
Dim oFolder As Outlook.MAPIFolder: Set oFolder = objNS.GetDefaultFolder(olFolderInbox)
Dim oTable As Outlook.Table
Dim oRow As Outlook.Row
Dim Filter As String
Dim textDisplay As String
Dim resultReceived As String
Filter = "[LastModificationTime] > '1/1/1900'"
Set oTable = oFolder.GetTable(Filter)
oTable.Columns.RemoveAll
With oTable.Columns
.Add ("SenderName")
'.Add ("SenderEmailAddress")
.Add ("Subject")
.Add ("LastModificationTime")
End With
Do Until (oTable.EndOfTable)
Set oRow = oTable.GetNextRow()
textDisplay = oRow("SenderName") & " | " & oRow("Subject") & " | " & oRow("LastModificationTime")
resultReceived = textDisplay & "§" & resultReceived
Loop
With Me.ListBox1
.Clear
.List = Split(resultReceived, "§")
.RemoveItem (Me.ListBox1.ListCount - 1)
End With
End Sub

Related

Sum all email.Items from Inbox + subfolders

Goodafternoon,
I am populating an list box with all the emails from Inbox + Subfolders, via Table object. This is working fine.
Then, with Doubleclick events from ListBox1, I am trying to open the email that is been selected. If the loop is going only through Inbox folder, it is going correct. But when I'm trying to loop through SubFolders from Inbox, it is not going. So I am trying to collect(sum) all the emails from Inbox + subfolder in one:
Set InboxItems = SubFolder.Items
But offcorse it is not working. What can be done?
my code:
Option Explicit
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Dim objNS As Outlook.namespace: Set objNS = GetNamespace("MAPI")
Dim oFolder As Outlook.MAPIFolder: Set oFolder = objNS.GetDefaultFolder(olFolderInbox)
Dim i As Long
Dim j As Long
Dim InboxItems As Outlook.Items
Dim thisEmail As Outlook.MailItem
Dim SubFolder As Outlook.MAPIFolder
Dim myArray() As String
Dim Folders As New Collection
Dim entryID As New Collection
Dim StoreID As New Collection
Call GetFolder(Folders, entryID, StoreID, oFolder)
myArray = ConvertToArray(indexEmailInbox)
For j = 1 To Folders.Count
Set SubFolder = Application.Session.GetFolderFromID(entryID(j), StoreID(j))
Set InboxItems = SubFolder.Items
Next
For i = LBound(myArray) To UBound(myArray)
If Me.ListBox1.Selected(i) = True Then
If TypeName(InboxItems.Item(onlyDigits(myArray(i)))) = "MailItem" Then ' it's an email
'MsgBox onlyDigits(myArray(UBound(myArray) - i - 1))
Set thisEmail = InboxItems.Item(onlyDigits(myArray(UBound(myArray) - i - 1)))
Unload Me
thisEmail.Display
Exit Sub
End If
End If
Next i
End Sub
Function ConvertToArray(ByVal value As String)
value = StrConv(value, vbUnicode)
ConvertToArray = Split(Left(value, Len(value) - 1), "§")
End Function
Sub GetFolder(folders As Collection, entryID As Collection, StoreID As Collection, fld As MAPIFolder)
Dim SubFolder As MAPIFolder
folders.Add fld.FolderPath
entryID.Add fld.entryID
StoreID.Add fld.StoreID
For Each SubFolder In fld.folders
GetFolder folders, entryID, StoreID, SubFolder
Next SubFolder
ExitSub:
Set SubFolder = Nothing
End Sub
You may .Add items to a collection one at a time.
Option Explicit ' Consider this mandatory
' Tools | Options | Editor tab
' Require Variable Declaration
' If desperate declare as Variant
Private Sub collection_Emails_Folder_And_Subfolders()
Dim objFolder As folder
Dim myItemsCol As New Collection
Dim i As Long
Dim myItems As Items
Set objFolder = Session.PickFolder
If objFolder Is Nothing Then
Exit Sub
End If
'Set objFolder = Session.GetDefaultFolder(olFolderInbox)
processFolder objFolder, myItemsCol
' Methods available are limited to:
' Add, Count, Item and Remove
Debug.Print vbCr & "Final total - myItemsCol.Count: " & myItemsCol.Count
' You may access item properties
For i = 1 To myItemsCol.Count
Debug.Print " " & i & ": " & myItemsCol(i).ReceivedTime, myItemsCol(i).subject
Next i
End Sub
Private Sub processFolder(ByVal objFolder As folder, ByVal myItemsCol As Collection)
' https://stackoverflow.com/questions/2272361/can-i-iterate-through-all-outlook-emails-in-a-folder-including-sub-folders
Dim EmailCount As Long
Dim myItem As Object
Dim myItems As Items
Dim i As Long
Dim oFolder As folder
Debug.Print vbCr & "objFolder: " & objFolder
EmailCount = objFolder.Items.Count
Debug.Print " EmailCount...: " & EmailCount
If EmailCount > 0 Then
Set myItems = objFolder.Items
myItems.Sort "[ReceivedTime]", False ' oldest to newest
For i = 1 To myItems.Count
'Debug.Print " " & i & ": " & myItems(i).ReceivedTime, myItems(i).subject
myItemsCol.Add myItems(i)
Next
End If
Debug.Print " Running total: " & myItemsCol.Count
If (objFolder.Folders.Count > 0) Then
For Each oFolder In objFolder.Folders
processFolder oFolder, myItemsCol
Next
End If
End Sub
You should be able to replace InboxItems with myItemsCol.
If TypeName(myItemsCol.Item((onlyDigits(myArray(i)))) = "MailItem" Then ' it's an email
Set thisEmail = myItemsCol.Item(onlyDigits(myArray(UBound(myArray) - i - 1)))
It seems you just need to iterate over all subfolders in Outlook to get the number of items per folder.
Sub Test()
Set objOutlook = CreateObject( "Outlook.Application" )
Set objNamespace = objOutlook.GetNamespace( "MAPI" )
Set folders = objNamespace.DefaultStore.GetRootFolder().Folders
EnumFolders folders
End Sub
Dim counter as Long = 0
' recursively invoked function
Sub EnumFolders(folders)
For Each folder In folders
Debug.Print folder.FolderPath
Debug.Print folder.Count
counter = counter + folder.Items.Count
EnumFolders folder.Folders
Next
End Sub

Outlook mail item multiple restrict methods

I am trying to apply a filter using the restrict method of mail items using Outlook VBA. Below code works fine if I use only one restrict method based on Category_Filter variable, but when I try to use two restrict methods,
Somehow my Flag_Filter fails.
I believe I am making some mistake in concatenation for Flag_Filter and need some clue here.
Sub ApplyFilters()
Dim ol As Outlook.Application
Dim ns As Outlook.NameSpace
Dim fol As Outlook.Folder
Dim i As Object
Dim mi As Outlook.MailItem
Dim OrderNumber, Category_Filter, Flag_Filter As String
Set ol = New Outlook.Application
Set ns = ol.GetNamespace("MAPI")
Set fol = ns.GetDefaultFolder(olFolderInbox)
OrderNumber = "GCU5689"
Category_Filter = "[Categories] = 'Textile'"
Flag_Filter = "[FlagRequest] = " & OrderNumber
For Each i In fol.Items.Restrict(Category_Filter).Restrict(Flag_Filter)
' Some actions
Next i
End Sub
You cannot restrict a restricted collection. You need to combine the restrictions (using "and" ) into a single query and call Restrict only once.
like #Dmitry Streblechenko says use And Operator (Visual Basic) MSDN with your Outlook restrict method
Example
Option Explicit
Private Sub Examples()
Dim olNs As Outlook.NameSpace
Dim Inbox As Outlook.MAPIFolder
Dim Items As Outlook.Items
Dim Msg As String
Dim i As Long
Dim Filter As String
Set olNs = Application.GetNamespace("MAPI")
Set Inbox = olNs.GetDefaultFolder(olFolderInbox)
Filter = "[Categories] = 'Textile' And [FlagRequest] = 'Follow up'"
Set Items = Inbox.Items.Restrict(Filter)
Msg = Items.Count & " Items in " & Inbox.Name
If MsgBox(Msg, vbYesNo) = vbYes Then
For i = Items.Count To 1 Step -1
Debug.Print Items(i) 'Immediate Window
Next
End If
End Sub
More examples here
for variable you can use the chr(34) function to represent the double quote (whose ASCII character value is 34) that is used as an escape character or use double quote.
Example
Dim OrderNumber As String
OrderNumber = "GCU5689"
Filter = "[Categories] = 'Textile' And [FlagRequest] = '" & OrderNumber & "'"
In general you may apply multiple actions one at a time. Such an approach allows easier troubleshooting.
Option Explicit ' Consider this mandatory
' Tools | Options | Editor tab
' Require Variable Declaration
Sub ApplyFilters()
Dim ns As NameSpace
Dim fol As folder
Dim i As Object
Dim mi As MailItem
Dim resItms As Items
Dim OrderNumber As String
Dim Category_Filter As String
Dim Flag_Filter As String
Set ns = GetNamespace("MAPI")
Set fol = ns.GetDefaultFolder(olFolderInbox)
OrderNumber = "GCU5689"
Category_Filter = "[Categories] = 'Textile'"
Flag_Filter = "[FlagRequest] = " & OrderNumber
Set resItms = fol.Items.Restrict(Category_Filter)
Debug.Print "Items after first restrict: " & resItms.Count
Set resItms = resItms.Restrict(Flag_Filter)
Debug.Print "Items after second restrict: " & resItms.Count
For Each i In resItms
' Some actions
' If, for instance, a property is only found on mailitems
If i.Class = olmail Then
Set mi = i
Debug.Print mi.Subject
End If
Next
End Sub

Getting attachment file size in outlook VBA

I receive emails and would like to save size of attachment in Excel sheet.
I can save size of email but I can't get size of attachment.
I looked up attachment.Size Property in MSDN, but it doesn't work. Could you please take a loop my for loop? I've attached my code below. I appreciate it if anyone would help.
Sub GetFromOutlook()
Dim OutlookApp As Outlook.Application
Dim OutlookNamespace As Namespace
Dim OutlookMail As Variant
Dim Folder As MAPIFolder
Dim olItems As Outlook.Items
Dim myItems As Outlook.Items
Dim olShareName As Outlook.Recipient
Dim dStart As Date
Dim dEnd As Date
Dim i As Integer
Dim sFilter As String
Dim sFilterLower As String
Dim sFilterUpper As String
Dim sFilterSender As String
'========================================================
'access to shared mailbox to get items
'========================================================
Set OutlookApp = New Outlook.Application
Set OutlookNamespace = OutlookApp.GetNamespace("MAPI")
Set olShareName = OutlookNamespace.CreateRecipient("teammailbox#example.ca")
Set Folder = Session.GetSharedDefaultFolder(olShareName, olFolderInbox).Folders("Subfolder1").Folders("Subfolder2")
Set olItems = Folder.Items
dStart = Range("From_Date").Value
dEnd = Range("To_Date").Value
'========================================================
'Conditions for restrict to get items specificed date
'========================================================
sFilterLower = "[ReceivedTime] > '" & Format(dStart, "ddddd h:nn AMPM") & "'"
sFilterUpper = "[ReceivedTime] < '" & Format(dEnd, "ddddd h:nn AMPM") & "'"
sFilterSender = "[SenderName] = ""jon.doe#example.com"""
'========================================================
'Restrict emails followed by above conditions
'========================================================
Set myItems = olItems.Restrict(sFilterLower)
Set myItems = myItems.Restrict(sFilterUpper)
Set myItems = myItems.Restrict(sFilterSender)
'========================================================
'items(emails) display in worksheets
'========================================================
i = 1
For Each myItem In myItems
MsgBox myItem.Attachments.Size
Range("eMail_subject").Offset(i, 0).Value = myItem.Subject
Range("eMail_date").Offset(i, 0).Value = Format(myItem.ReceivedTime, "h:nn")
Range("eMail_size").Offset(i, 0).Value = myItem.Size
i = i + 1
Next myItem
Set Folder = Nothing
Set OutlookNamespace = Nothing
Set OutlookApp = Nothing
End Sub
Sub sbClearCellsOnlyData()
Rows("5:" & Rows.Count).ClearContents
End Sub
The VBEditor has a great built-in tool for debugging. If you press Ctrl+F9, while selecting the myItem text with your mouse cursor you would be able to see it. It is called "Local Window" (or in German - "überwachungsausdrücke")
Long stroy short, give it a try like this in the code:
MsgBox myItem.Attachments.Item(1).Size
Instead of:
MsgBox myItem.Attachments.Size
Before doing this, it is a good idea to see whether the attachment exists:
If myItem.Attachments.Count > 0 Then
Attachment.Size property is not the size of the file blob - it is the size of the attachment record in the message store, which includes the attachment data, file name, and various other MAPI properties.
If you only want the file data, you can either use Extended MAPI (C++ or Delphi) to open the blob (PR_ATTACH_DATA_BIN) as IStream and call IStream::Stat. If you are limited to using the Outlook Object Model only, the only workaround is saving the attachment as file (Attachment.SaveAsFile) and then retrieving the file size.
I got hint from #Vityata and my below code is fixed and it works now.
For Each myItem In myItems
Range("eMail_subject").Offset(i, 0).Value = myItem.Subject
Range("eMail_date").Offset(i, 0).Value = Format(myItem.ReceivedTime, "h:nn")
If myItem.Attachments.Count > 0 Then
Range("eMail_size").Offset(i, 0).Value = myItem.Attachments.Item(1).Size / 1024 'convert to 3 digits (KB)
Else
Range("eMail_size").Offset(i, 0).Value = "No attached file"
End If
i = i + 1
Next myItem

Excel VBA Logic idea

My script is for tracking emails with subject and received time.
I receive 4 emails daily and I need to make reports about when email receiving.
My question is I create check box that allow which emails I want to track, but I don't know how can I make logically. First I tried , I created 15 if statements. I know it's not good so I am looking for new logic. I have attached my code below.
Please share your knowledge.
Plus, restrict method is for specified date and sender.
Sub GetFromOutlook()
Dim OutlookApp As Outlook.Application
Dim OutlookNamespace As Namespace
Dim OutlookMail As Variant
Dim Folder As MAPIFolder
Dim olItems As Outlook.Items
Dim myItems As Outlook.Items
Dim olShareName As Outlook.Recipient
Dim dStart As Date
Dim dEnd As Date
Dim i As Integer
Dim sFilter As String
Dim sFilterLower As String
Dim sFilterUpper As String
Dim sFilterSender As String
'========================================================
'access to shared mailbox to get items
'========================================================
Set OutlookApp = New Outlook.Application
Set OutlookNamespace = OutlookApp.GetNamespace("MAPI")
Set olShareName = OutlookNamespace.CreateRecipient("Mailbox.teamshared#example.ca")
Set Folder = Session.GetSharedDefaultFolder(olShareName, olFolderInbox).Folders("subfolder1").Folders("subfolder2")
Set olItems = Folder.Items
'========================================================
'If check box is checked
'========================================================
dStart = Range("From_Date").Value
dEnd = Range("To_Date").Value
'========================================================
'Conditions for restrict to get items specificed date
'========================================================
sFilterLower = "[ReceivedTime] > '" & Format(dStart, "ddddd h:nn AMPM") & "'"
sFilterUpper = "[ReceivedTime] < '" & Format(dEnd, "ddddd h:nn AMPM") & "'"
sFilterSender = "[SenderName] = ""no-reply#example.com"""
'========================================================
'Restrict emails followed by above conditions
'========================================================
Set myItems = olItems.Restrict(sFilterLower)
Set myItems = myItems.Restrict(sFilterUpper)
Set myItems = myItems.Restrict(sFilterSender)
'========================================================
'items(emails) display in worksheets
'========================================================
i = 1
For Each myItem In myItems
Range("eMail_subject").Offset(i, 0).Value = myItem.Subject
Range("eMail_date").Offset(i, 0).Value = Format(myItem.ReceivedTime, "h:nn")
'Convert size KB
If myItem.Attachments.Count > 0 Then
Range("eMail_size").Offset(i, 0).Value = myItem.Attachments.Item(1).Size / 1024
Else
Range("eMail_size").Offset(i, 0).Value = "No attached file"
End If
i = i + 1
Next myItem
Set Folder = Nothing
Set OutlookNamespace = Nothing
Set OutlookApp = Nothing
Else
MsgBox "You have check at least one"
End If
End Sub

Return one newest instance of the mail, based on subject, from multiple subfolders

I have a search for items, in subfolders of the Inbox, based on subject line.
I am trying to return the most recent mail and have been using the code:
Items.Sort "[ReceivedTime]", True
I also tried CreationTime and SentOn in between the brackets.
The search returns mails with the same subject line in the following order:
9/23/2016 9:31 AM
10/19/2016 12:57 PM
9/29/2016 10:54 AM
My code:
Dim Fldr As Outlook.MAPIFolder
Dim Items As Outlook.Items
Dim olMail As Variant
Set oOLapp = CreateObject("Outlook.application")
Set olNs = oOLapp.GetNamespace("MAPI")
For step = 1 To MaxCount
Set Fldr = olNs.GetDefaultFolder(olFolderInbox)
For Each Fldr in Fldr.Folders
Set Items = Fldr.Items
Items.Sort "[ReceivedTime]", True
For Each olMail in Items
If InStr(olMail.Subject, "Text" & Cstr(step))
olMail.Display Then
Set Msg = oOLapp.CreateItem(olMailItem)
.Attachments.Add olMail, olEmbeddeditem
Set Msg = Nothing
End If
Next
Next
Next
I want the one newest instance of the mail.
I also tried the code below where people seem to have the most success when trying to retrieve the most recent code.
I get
Error404 "Array index out of bounds"
For step = 1 To MaxCount
Set Fldr = olNs.GetDefaultFolder(olFolderInbox)
For i = Fldr.Folders.Count To 1 Step -1
Set Fldr = Fldr.Folders(i)
For a = Fldr.Items.Count To 1 Step - 1
Set olMail = Fldr.Items(a)
//Search and attachment code. See previous code
Next
Next
Next
RESULT:
My code pulls the mail in sequential order based on the folders it looks in. So the mail with the earliest time stamps went into a folder that appeared before the other mail so that is why my code kept pulling the earliest one instead of the latest one.
Option Explicit ' Consider this mandatory
' Tools | Options | Editor tab
' Require Variable Declaration
' If desperate declare as Variant
Sub AdvSearchForStr()
Dim strSearch As String
Dim rsts As Results
Dim i As Long
Dim rstObj As Object
Dim myMsg As MailItem
strSearch = "Test"
Dim strFilter As String
strFilter = "urn:schemas:httpmail:subject LIKE '%" & strSearch & "%'"
Debug.Print strFilter
Dim strScope As String
'strScope = "'Inbox', 'Sent Items', 'Tasks'"
strScope = "'Inbox'"
Dim objSearch As Search
Set objSearch = Application.AdvancedSearch(Scope:=strScope, Filter:=strFilter, SearchSubFolders:=True, Tag:="SearchFolder")
'Save the search results to a searchfolder
'objSearch.Save (strSearch)
' Delay to allow search to complete
' The Application.AdvancedSearchComplete event appears to be broken
' https://learn.microsoft.com/en-us/office/vba/api/outlook.application.advancedsearchcomplete
Dim waitTime As Long
Dim delay As Date
waitTime = 1 ' in seconds - adjust as needed
Debug.Print vbCr & "Wait start: " & Now
delay = DateAdd("s", waitTime, Now)
Debug.Print "Wait until: " & delay
Do Until Now > delay
DoEvents
Loop
Set rsts = objSearch.Results
Debug.Print " rsts.Count: " & rsts.Count
If rsts.Count > 0 Then
rsts.Sort "[ReceivedTime]", True
Set rstObj = rsts(1)
rstObj.Display
Set myMsg = CreateItem(olMailItem)
myMsg.Attachments.Add rstObj, olEmbeddeditem
myMsg.Display
Else
Debug.Print "no mail found."
End If
End Sub
I had no problem running the following script in - all the messages are in the expected order - from older to newest. Did you mean to sort newest to oldest?
set folder = Application.ActiveExplorer.CurrentFolder
set items = folder.Items
items.Sort "[ReceivedTime]", False
For Each msg in items
Debug.Print msg.ReceivedTime & " " & msg.Subject
next