This macro collects the fields from each Outlook message in a selection and writes the values of the fields to an Excel worksheet. It's easily adapted to work with any field and any Outlook item type.
In Excel 2016, rCount is finding the last USED line, not the next blank line. Use rCount = rCount + 1 to move down one line.
Updated November 25 2017 to get all recipient addresses.
Updated October 20 2017 to create a new workbook (user will need to save it). Also added column names and adjusted the column widths.
If you want to run the macro on all messages in the selected folder, use this file. In addition, it will create the workbook if it doesn't exist and add the columns headers if needed.
An Excel version of this macro is available in a workbook template here or as a text file here. The workbook code removes hyperlinked URLs from the messages (for easier reading in Excel).
Option Explicit Sub CopyToExcel() Dim xlApp As Object Dim xlWB As Object Dim xlSheet As Object Dim rCount As Long Dim bXStarted As Boolean Dim enviro As String Dim strPath As String Dim currentExplorer As Explorer Dim Selection As Selection Dim olItem As Outlook.MailItem Dim obj As Object Dim strColA, strColB, strColC, strColD, strColE As String ' Get Excel set up On Error Resume Next Set xlApp = GetObject(, "Excel.Application") If Err <> 0 Then Application.StatusBar = "Please wait while Excel source is opened ... " Set xlApp = CreateObject("Excel.Application") bXStarted = True End If On Error GoTo 0 '## Open a specific workbook to input the data 'the path of the workbook under the windows user account 'enviro = CStr(Environ("USERPROFILE")) ' strPath = enviro & "\Documents\test.xlsx" ' Set xlWB = xlApp.Workbooks.Open(strPath) ' Set xlSheet = xlWB.Sheets("Sheet1") '## End Specific workbook '## Use New Workbook Set xlWB = xlApp.Workbooks.Add Set xlSheet = xlWB.Sheets("Sheet1") '## end use new workbook ' Add column names xlSheet.Range("A1") = "Sender" xlSheet.Range("B1") = "Sender address" xlSheet.Range("C1") = "Message Body" xlSheet.Range("D1") = "Sent To" xlSheet.Range("E1") = "Recieved Time" ' Process the message record On Error Resume Next 'Find the next empty line of the worksheet rCount = xlSheet.Range("A" & xlSheet.Rows.Count).End(-4162).Row 'needed for Exchange 2016. Remove if causing blank lines. rCount = rCount + 1 ' get the values from outlook Set currentExplorer = Application.ActiveExplorer Set Selection = currentExplorer.Selection For Each obj In Selection Set olItem = obj 'collect the fields strColA = olItem.SenderName strColB = olItem.SenderEmailAddress strColC = olItem.Body strColD = olItem.To strColE = olItem.ReceivedTime '### Get all recipient addresses ' instead of To names Dim strRecipients As String Dim Recipient As Outlook.Recipient For Each Recipient In olItem.Recipients strRecipients = Recipient.Address & "; " & strRecipients Next Recipient strColD = strRecipients '### end all recipients addresses '### Get the Exchange address ' if not using Exchange, this block can be removed Dim olEU As Outlook.ExchangeUser Dim oEDL As Outlook.ExchangeDistributionList Dim recip As Outlook.Recipient Set recip = Application.Session.CreateRecipient(strColB) If InStr(1, strColB, "/") > 0 Then ' if exchange, get smtp address Select Case recip.AddressEntry.AddressEntryUserType Case OlAddressEntryUserType.olExchangeUserAddressEntry Set olEU = recip.AddressEntry.GetExchangeUser If Not (olEU Is Nothing) Then strColB = olEU.PrimarySmtpAddress End If Case OlAddressEntryUserType.olOutlookContactAddressEntry Set olEU = recip.AddressEntry.GetExchangeUser If Not (olEU Is Nothing) Then strColB = olEU.PrimarySmtpAddress End If Case OlAddressEntryUserType.olExchangeDistributionListAddressEntry Set oEDL = recip.AddressEntry.GetExchangeDistributionList If Not (oEDL Is Nothing) Then strColB = olEU.PrimarySmtpAddress End If End Select End If ' ### End Exchange section 'write them in the excel sheet xlSheet.Range("A" & rCount) = strColA ' sender name xlSheet.Range("B" & rCount) = strColB ' sender address xlSheet.Range("C" & rCount) = strColC ' message body xlSheet.Range("D" & rCount) = strColD ' sent to xlSheet.Range("E" & rCount) = strColE ' recieved time 'Next row rCount = rCount + 1 Next ' size the cells xlSheet.Columns("A:E").EntireColumn.AutoFit xlSheet.Columns("C:C").ColumnWidth = 100 xlSheet.Columns("D:D").ColumnWidth = 30 xlSheet.Range("A2").Select xlSheet.Columns("A:E").VerticalAlignment = xlTop xlApp.Visible = True ' to save but not close 'xlWB.Save ' to save and close ' xlWB.Close 1 ' If bXStarted Then ' xlApp.Quit ' End If ' end save and close Set olItem = Nothing Set obj = Nothing Set currentExplorer = Nothing Set xlSheet = Nothing Set xlWB = Nothing Set xlApp = Nothing End Sub
Automate using an ItemAdd or Run a Script Macro
With a few slight modifications, we can watch a folder for new messages and process new mail as it arrives.
This set of macros needs to go into ThisOutlookSession.
Warning: If too many messages come in at one time, the macro could fail.
If you need to filter the messages that added to the spreadsheet you have two options: use an If statement to exit the macro or convert it to a Run a Rule script.
If you use an if Statement, it should be the first line of the bjItems_ItemAdd macro.
Private Sub objItems_ItemAdd(ByVal Item As Object)
If InStr(1, Item.Subject, "Tip") = 0 Then Exit Sub
For a run a script rule, delete Private Sub objItems_ItemAdd(ByVal Item As Object) and all of the lines above it then use this as the macro name and create your rule.
Public Sub ShowMessage(Item As Outlook.MailItem)
Option Explicit Private objNS As Outlook.NameSpace Private WithEvents objItems As Outlook.Items Private Sub Application_Startup() Dim objWatchFolder As Outlook.Folder Set objNS = Application.GetNamespace("MAPI") 'Set the folder and items to watch: ' Use this for a folder in your default data file Set objWatchFolder = objNS.GetDefaultFolder(olFolderInbox) ' to watch a folder in a non-default data file ' seehttp://slipstick.me/qf for GetFolderPath Function ' Set objWatchFolder = GetFolderPath("me@domain.com\Inbox") Set objItems = objWatchFolder.Items Set objWatchFolder = Nothing End Sub Private Sub objItems_ItemAdd(ByVal Item As Object) Dim xlApp As Object Dim xlWB As Object Dim xlSheet As Object Dim rCount As Long Dim bXStarted As Boolean Dim enviro As String Dim strPath As String Dim strColB, strColC, strColD, strColE, strColF As String ' Get Excel set up enviro = CStr(Environ("USERPROFILE")) 'the path of the workbook strPath = enviro & "\Documents\test.xlsx" On Error Resume Next Set xlApp = GetObject(, "Excel.Application") If Err <> 0 Then Application.StatusBar = "Please wait while Excel source is opened ... " Set xlApp = CreateObject("Excel.Application") bXStarted = True End If On Error GoTo 0 'Open the workbook to input the data Set xlWB = xlApp.Workbooks.Open(strPath) Set xlSheet = xlWB.Sheets("Sheet1") ' Process the message record On Error Resume Next 'Find the next empty line of the worksheet rCount = xlSheet.Range("B" & xlSheet.Rows.Count).End(-4162).Row 'needed for Exchange 2016. Remove if causing blank lines. rCount = rCount + 1 'collect the fields strColC = Item.SenderEmailAddress strColB = Item.SenderName strColD = Item.Body strColE = Item.To strColF = Item.ReceivedTime ' Get the Exchange address ' if not using Exchange, this block can be removed Dim olEU As Outlook.ExchangeUser Dim oEDL As Outlook.ExchangeDistributionList Dim recip As Outlook.Recipient Set recip = Application.session.CreateRecipient(strColC) If InStr(1, strColC, "/") > 0 Then ' if exchange, get smtp address Select Case recip.AddressEntry.AddressEntryUserType Case OlAddressEntryUserType.olExchangeUserAddressEntry Set olEU = recip.AddressEntry.GetExchangeUser If Not (olEU Is Nothing) Then strColC = olEU.PrimarySmtpAddress End If Case OlAddressEntryUserType.olOutlookContactAddressEntry Set olEU = recip.AddressEntry.GetExchangeUser If Not (olEU Is Nothing) Then strColC = olEU.PrimarySmtpAddress End If Case OlAddressEntryUserType.olExchangeDistributionListAddressEntry Set oEDL = recip.AddressEntry.GetExchangeDistributionList If Not (oEDL Is Nothing) Then strColC = olEU.PrimarySmtpAddress End If End Select End If ' End Exchange section 'write them in the excel sheet xlSheet.Range("B" & rCount) = strColB xlSheet.Range("c" & rCount) = strColC xlSheet.Range("d" & rCount) = strColD xlSheet.Range("e" & rCount) = strColE xlSheet.Range("f" & rCount) = strColF 'Next row rCount = rCount + 1 xlWB.Close 1 If bXStarted Then xlApp.Quit End If Set xlApp = Nothing Set xlWB = Nothing Set xlSheet = Nothing End Sub
How to use macros
First: You will need macro security set to low during testing.
To check your macro security in Outlook 2010 or 2013, go to File, Options, Trust Center and open Trust Center Settings, and change the Macro Settings. In Outlook 2007 and older, it’s at Tools, Macro Security.
After you test the macro and see that it works, you can either leave macro security set to low or sign the macro.
Open the VBA Editor by pressing Alt+F11 on your keyboard.
To put the code in a module:
- Right click on Project1 and choose Insert > Module
- Copy and paste the macro into the new module.
More information as well as screenshots are at How to use the VBA Editor
Hi Diane - I love your work, and maybe depend on it a little too much!
I'm having an odd issue with your code.
When I have a block of emails selected (e.g. a month's worth), the macro gets to a point as it moves down the list of emails where it will not update the sender / sender address / message body / Sent To values BUT the Received Time will properly output row-by-row.
It isn't the particular email that is causing issues, because if I select the day the email is sent, I will get proper output - one spreadsheet row per email.
Because it works up to a point and then stops working for some (not all) of the olItem fields, I can't even begin to troubleshoot it. Can you help?
Any idea what the point is? A few others reported problems at something like 200 messages - I understood for them that it just stopped - not that one field worked. I exported some 40,000 messages last weeks, no problem (it took 15 min or more to complete.)
Are the messages all emails? If you have reports, meeting invites or other non-mail mailed items, it should error but could be trying to export them - they don't have all the same fields though. (Should have sender, subject, and date.)
if you need non-mail items exported, change Dim olItem As Outlook.MailItem to Dim olItem As Object, if you don't need them exported, make this change:
For Each obj In Selection
if obj,messageclass = "IPM.Note" then
Set olItem = obj
----snipped---
rCount = rCount + 1
end if
Next
Hi Diane
I don't know if the original commenter was me or not, but I have a suspiciously similar problem after 244 rows output out of 2016 total. Have tried the two variations you have mentioned above, with no success. Did you ever get a fix that worked for the other people you mentioned?
No, not that I know of.
It always dies after 244 rows?
Just run it again with a different selection of emails (a filtered month; 381 total), sorted ascending.
This time, row 248 (including header) is the last row with different sender/sender address/message body/sent to. These values just repeat from row 249 onward (as mentioned, only the Received Time corresponds to an email after this point).
Do the same thing again with that filtered month but in descending order, and the last properly output row is row 238.
Perplexing.
FYI, 248 ascending vs 238 descending is not a typo.
hi how can i get only email address (to,cc,from field) all folders using that script, and duplicate removal.
If you want all addresses it was sent to, you need to get the recipients.
'### Get all recipient addresses
' instead of To names
Dim strRecipients As String
Dim Recipient As Outlook.Recipient
For Each Recipient In olItem.Recipients
strRecipients = Recipient.Address & "; " & strRecipients
Next Recipient
strColD = strRecipients
'### end all recipients addresses
Remove the fields you don't want here -
'write them in the excel sheet
xlSheet.Range("A" & rCount) = strColA ' sender name
xlSheet.Range("B" & rCount) = strColB ' sender address
xlSheet.Range("C" & rCount) = strColC ' message body
xlSheet.Range("D" & rCount) = strColD ' sent to
xlSheet.Range("E" & rCount) = strColE ' recieved time
Hello. In your email, you know how you can put a category heading on an email... is there a way to import that information as well as the subject, body, and sender into Excel? Thanks....
Dang clipboard = nothing like pasting the wrong answer. :)
Yes, you can get the categories - it is .categories:
For example:
strColE = olItem.Categories
The export function built into outlook would include it - but the body field is messed up when exporting.
The macro would use this;
'collect the fields
strColC = Item.SenderEmailAddress
strColB = Item.SenderName
strColD = Item.Body
strColE = Item.To
strColF = Item.ReceivedTime
strColG = Item.Categories
'write them in the excel sheet
xlSheet.Range("B" & rCount) = strColB
xlSheet.Range("c" & rCount) = strColC
xlSheet.Range("d" & rCount) = strColD
xlSheet.Range("e" & rCount) = strColE
xlSheet.Range("f" & rCount) = strColF
xlSheet.Range("g" & rCount) = strColG
Hey there, nice addition btw !
Also wanted to know like Tommy if the "in Folder" field is possible to be added through macro.
item.Parent will get the folder name the item is in.
item.Parent.FolderPath gets the folder path
Hi Diane Thank you for all the great vba code for exporting emails to Excel. It has been years since I coded with vba so your examples made the project go much faster. I am having one issue. The Received Date in the email is in UK date format dd-mm-yyyy. When it exports, it exports to a USA date format - mm-dd-yyyy. So 08-10-2020 becomes 10-08-2020 so to us - that reads 10 Aug 2020 not 8 Oct 2020. Changing the format in Exceldoes not get around this problem. Do you have any ideas on how to resolve? Kind regards
Try using this:
Format(olItem.ReceivedTime, "dd-mm-yyyy")
Is it possible to grab the data from .msg files within a folder rather than from Outlook?
Get the Send/receive date msg files saved on the computer? Only if you open the message - its not saved in the metadata exposed in the file system. So yeah, its possible, but is a bit slower.
Oh this is what I'm looking for but need to add other fields. Category, Name of Attachment, In Folder, In Sub-folder.
Please can you help?
Hi Dianne,
Thanks for the great code and your assume comments!
Just one thing i could not find back on your site.
How can i loop trough folders directly underneed from an shared EX mailbox?
->Inbox (loop trough each mail)
------->folder 1 to 40 (loop trough each mail)
Thank You!!!!!!!
In addition, this is what i use successfully to access the inbox.
Hey Dianne,
Now iam baffled. If i run the code, slight modified in outlook, no problems.
If i run it trough excel, i get only the Inbox, not the sub-folders.
objFolder shows only 1 folder, not my 40 sub-folders. Any thoughts?
Hi Dianne,
Nevermind got it.
VBA is awsume, i had no idea how cool it is!!!!