My outlook VBA code drops the odd email - vba
I put together some VBA code for Outlook 2007 which has been working predominantly fine.
Its basically designed to check incoming messages and store the subject, body etc into a database and the attachment into a folder. In general, it works fine, but out of 100 messages or so, it drops the odd email.
I previously had a problem where some emails were not being processed and stored in the database, but then discovered there was an issue with illegal characters, which i have solved now, so that cant be it. I've compared the emails being dropped to the one's that arent, in terms of message header, content to and from fields and i cant see any difference between the two emails at all, so am completely perplexed as to why they're being dropped. When i copy the content of the email and forward it back to the system again, the VBA code processes it fine.
I am pasting the code below (the code links to some modules which are used for checking illegal characters or concatenating strings)
Sub SaveIncomingEmails(Items As Outlook.MailItem) ' enable this to run macro inbound emails
Dim cnn As ADODB.Connection
Set cnn = New ADODB.Connection
' ================================================================
' Open a Connection using an ODBC DSN named "Delphi".
' ================================================================
cnn.Open "MyDB", "MyUsername", "MyPassword"
' ================================================================
' Constants declaration
' ================================================================
Const olFolderInbox = 6
Const olTxt = 0
' ================================================================
' variable declaration
' ================================================================
Dim ns As NameSpace
Dim Inbox As MAPIFolder
Dim Item As Object
Dim Atmt As Attachment
Dim FileName As String
Dim SenderName As String
Dim i As Integer
Dim strSQLquery As String
Dim strSQLquery1 As String
Dim strSQLGTDResourceQuery As String
Dim MessageHeader As String
Dim strCommandQuery As String
Dim strGTDIdQuery As String
Dim AttachmentStr As String
Dim strFailedRcp As String
Dim strSubject As String
Dim hasattachment As String
Dim AttachmentType As String
Dim SenderAuthorised As String
Dim strToEmail As String
Dim strFromEmail As String
Dim strBody As String
Dim strSentDate As String
Dim strReceivedDate As String
Dim StrUniqueID As String
Dim strCommandDate As String
Dim strDomain As String
Dim strBodyStripped As String
Dim strSubjectStripped As String
Dim rs As Object
Dim strGoalId As String
Dim strFile As String
Dim strSenderAccountDescription As String
Dim strContentType As String
Dim strMimeVersion As String
Dim strReceived As String
' ================================================================
' Intializing variables
' ================================================================
i = 0
Set objItem = Items
Set ns = GetNamespace("MAPI")
Set Inbox = ns.GetDefaultFolder(olFolderInbox)
Set objOutlook = CreateObject("Outlook.Application")
Set objNamespace = objOutlook.GetNamespace("MAPI")
Set objFolder = objNamespace.GetDefaultFolder(olFolderInbox)
Set colMailItems = objFolder.Items
Set Item = objItem
strToEmail = Items.To
strFromEmail = Items.SenderEmailAddress
strSubject = Items.Subject
strBody = Items.Body
strSentDate = Items.SentOn
strReceivedDate = Items.ReceivedTime
'Initialize variables in a given format
StrUniqueID = Format(Items.ReceivedTime, "ddmmyyyyhhnnss") & Items.SenderEmailAddress
strCommandDate = Format(Items.ReceivedTime, "mm/dd/yyyy_hh:nn:ss")
' Grab the sender domain by stripping the last portion of the email address using the getdomain function
strDomain = Module2.GetDomain(Items.SenderEmailAddress)
' Strip the body of illegal characters and replace with legal characters for insertion into SQL
strBodyStripped = Module3.RemoveIllegalCharacters(Items.Body)
strSubjectStripped = Module4.RemoveIllegalCharacters(Items.Subject)
AttachmentStr = "images/no_attachment.png"
' ================================================================
' ================================================================
' ================================================================
' =====================================================
' Check list of authorised senders for xsCRM commands.
' Populate email addresses here
' =====================================================
If (InStr(strFromEmail, "AuthorisedSender1#email.com") > 0) Or (InStr(strFromEmail, "AuthorisedSender2#email.com") > 0) Or (InStr(strFromEmail, "AuthorisedSender3#email.com") > 0) Then
SenderAuthorised = "true"
End If
' ======================================================
' ======================================================
' ======================================================
' ================================================================
' check if subject holds a command
' ================================================================
'check to see if email sender is authorised
If SenderAuthorised = "true" Then
' Check if the subject line contains the string xs4crm is true
If InStr(strSubject, "xs4crm") > 0 Then
'If its true then do this
strCommandQuery = "INSERT INTO XSCRMEMAILCOMMAND (" & vbCrLf & _
"FromEmail," & vbCrLf & _
"command," & vbCrLf & _
"date," & vbCrLf & _
"Body" & vbCrLf & _
") VALUES ('" & strFromEmail & "','" & strSubject & "',GETDATE(),'" & strBody & "')"
Set rs = cnn.Execute(strCommandQuery)
'Look for a GTDID string so that we can save data to resources table
If InStr(strSubject, "gtdid=") > 0 Then
'Set the hasattachment variable to zero since we only want to run this loop if there are no attachments
hasattachment = "0"
'Set the variable to 1 so that we that our next if statement can only run if there are no attachments
For Each Atmt In Item.Attachments
hasattachment = "1"
Next Atmt
If hasattachment = "0" Then
'Grab the GTDId so we know which goal this resource belongs too.
strGoalId = Module5.GetHeaderProperty(strSubject, "gtdid=", ";", 5)
'Save data to table
strGTDIdQuery = "INSERT INTO XSCRMGTDRESOURCES (" & vbCrLf & _
"GoalId," & vbCrLf & _
"insertdatetime" & vbCrLf & _
") VALUES ('" & strGoalId & "',GETDATE())"
Set rs = cnn.Execute(strGTDIdQuery)
End If
End If
End If
End If
' ================================================================
' ================================================================
' ================================================================
' ================================================================
' Create folders for atttachments
' ================================================================
' Save any attachments found
For Each Atmt In Item.Attachments
AttachmentStr = "images/attachment.png" 'because it has gone into attachment loop the icon is now required.
'Create the subfolder for the attachment if it doesnt exist based on sender domain
Dim fso
Dim fol As String
fol = "c:\OLAttachments\" & strDomain
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FolderExists(fol) Then
fso.CreateFolder (fol)
End If
' ================================================================
' ================================================================
' ================================================================
' ================================================================
' save attachments
' ================================================================
FileName = "C:\OLAttachments\" & strDomain & "\" & _
Format(Item.CreationTime, "ddmmyyyy-") & Items.SenderEmailAddress & "-" & Atmt.FileName
Atmt.SaveAsFile FileName
i = i + 1
strFile = Atmt.FileName
strSQLquery1 = "INSERT INTO XSCRMEMAILSATTACHMENTS (" & vbCrLf & _
"FileSavedIn," & vbCrLf & _
"ActualFileName," & vbCrLf & _
"UniqueIdentifier," & vbCrLf & _
"SendersEmail" & vbCrLf & _
") VALUES ('" & FileName & "','" & StrUniqueID & "','" & strFile & "','" & strFromEmail & "')"
Set rs = cnn.Execute(strSQLquery1)
'If there is a GTDCommand, then grab the GTDId so we know which goal this resource belongs too.
If InStr(strSubject, "gtdid=") > 0 Then
strGoalId = Module5.GetHeaderProperty(strSubject, "gtdid=", ";", 5)
End If
AttachmentType = ""
'If the attachment is png or jpg set attachment type string to image
If (InStr(Atmt.FileName, ".png") > 0) Or (InStr(Atmt.FileName, ".jpg") > 0) Then
AttachmentType = "image"
End If
'If attachment is .mov set attachment type string to video
If InStr(Atmt.FileName, ".mov") > 0 Then
AttachmentType = "video"
End If
'If the attachment is mp3 or m4a set attachment type string to audio
If (InStr(Atmt.FileName, ".mp3") > 0) Or (InStr(Atmt.FileName, ".m4a") > 0) Then
AttachmentType = "audio"
End If
'check to see if email sender is authorised
If SenderAuthorised = "true" Then
'If attachment type is an image, audio or video as per extensions above then populate the xscrmgtdresource table with following fields
If (InStr(Atmt.FileName, ".png") > 0) Or (InStr(Atmt.FileName, ".jpg") > 0) Or (InStr(Atmt.FileName, ".mov") > 0) Or (InStr(Atmt.FileName, ".m4a") > 0) Or (InStr(Atmt.FileName, ".mp3") > 0) Then
strSQLGTDResourceQuery = "INSERT INTO XSCRMGTDRESOURCES (" & vbCrLf & _
"GoalId," & vbCrLf & _
"Title," & vbCrLf & _
"Type," & vbCrLf & _
"insertdatetime," & vbCrLf & _
"ResourcePath," & vbCrLf & _
"UniqueIdentifier" & vbCrLf & _
") VALUES ('" & strGoalId & "','" & Atmt.FileName & "','" & AttachmentType & "',GETDATE(),'" & FileName & "','" & StrUniqueID & "')"
End If
Set rs = cnn.Execute(strSQLGTDResourceQuery)
End If
Next Atmt
' ================================================================
' ================================================================
' ================================================================
' ================================================================
' Setting up to work with the Email Message Header
' ================================================================
'This accesses the message header property and sets the variable MessageHeader
Const PR_TRANSPORT_MESSAGE_HEADERS = "http://schemas.microsoft.com/mapi/proptag/0x007D001E"
MessageHeader = objItem.PropertyAccessor.GetProperty(PR_TRANSPORT_MESSAGE_HEADERS)
If MessageHeader <> "" Then
End If
' ================================================================
' ================================================================
' ================================================================
' ================================================================
' Accessing the message header and collecting specific info for database tables
' ================================================================
strSenderAccountDescription = Module5.GetHeaderProperty(MessageHeader, "From:", "<", 5)
strContentType = Module5.GetHeaderProperty(MessageHeader, "Content-Type:", ";", 13)
strMimeVersion = Module5.GetHeaderProperty(MessageHeader, "MIME-Version:", vbNewLine, 13)
strReceived = Module5.GetHeaderProperty(MessageHeader, "Received:", "(", 9)
'As the x-failed-recipients property does not appear in ALL messageheaders, we have to first check if it is present
If InStr(MessageHeader, "X-Failed-Recipients:") > 0 Then
'Get the MessageHeader Property value
strFailedRcp = Module5.GetHeaderProperty(MessageHeader, "X-Failed-Recipients:", vbNewLine, 20)
'Else set the variable value to blank so that we still have something to supply to the SQL query
Else
strFailedRcp = ""
End If
' ================================================================
' ================================================================
' ================================================================
' ================================================================
' Save Email into the database DeplphiDude and table xsCRMEmails for attachment based emails and without attachments
' ================================================================
If InStr(strSubject, "xs4crm") = 0 Then 'only insert if the emails is not a command
strSQLquery = "INSERT INTO XSCRMEMAILS (" & vbCrLf & _
"XFailedRecipients," & vbCrLf & _
"Received," & vbCrLf & _
"MimeVersion," & vbCrLf & _
"ContentType," & vbCrLf & _
"SendersAccountDescription," & vbCrLf & _
"FromEmail," & vbCrLf & _
"ToEmail," & vbCrLf & _
"Subject," & vbCrLf & _
"Body," & vbCrLf & _
"SentDate," & vbCrLf & _
"ReceivedDate," & vbCrLf & _
"UniqueIdentifier," & vbCrLf & _
"Status," & vbCrLf & _
"AttachmentIcon," & vbCrLf & _
"AssignedToUser," & vbCrLf & _
"EmailHeader" & vbCrLf & _
") VALUES ('" & strFailedRcp & "','" & strReceived & "','" & strMimeVersion & "','" & strContentType & "','" & strSenderAccountDescription & "', '" & strFromEmail & "','" & strToEmail & "','" & strSubjectStripped & "','" & strBodyStripped & "','" & strSentDate & "','" & strReceivedDate & "','" & StrUniqueID & "','EmailStatus_New','" & AttachmentStr & "','','" & Module4.RemoveIllegalCharacters(MessageHeader) & "')"
Set rs = cnn.Execute(strSQLquery)
End If
' ================================================================
' final steps
' ================================================================
'Delete email
objItem.Delete
Set objItem = Nothing
Set Atmt = Nothing
' ================================================================
' close connection to the sql server and end the program
' ================================================================
cnn.Close
End Sub
You should add some logging to help track down the problem.
I haven't used this personally, but maybe give it a go: Log4VBA
Also, you should add error handling:
Error Handling and Debugging Tips for Access 2007, VB, and VBA
Error Handling In VBA
First you do not say which part of your process is not working. You have showed a routine that does not fire by itself, it must be called by something else. This something else must have some conditions attached to it to call your routine. What are they? Can you show the workings of this.
If you are using a rule then could you show the conditions of the rule. Further what about if instead of a rule we code for the event in the VBEditor so that you can maybe see this event happening as well? Here is what I am talking about and there is example code there on how to do it MSDN Application_New_MAIL
Next I agree with everyone else that you need some logging, there is so much going on and it is impossible to tell where you cod is falling over. If I were you I would get an email that does not work and send it to yourself and have a break point right at the beginning of your code so that you can see a. That your code is actually being called and then where it is failing.
Related
Is it possible to identify through OUTLOOK triggers/events to which shared mail box has received a new email?
We are trying to store new mail item components into excel and assign tkt id, have tried doing it with single shared mailbox and succeeded but we want to implement same for 20 shared mail boxes. how can outlook vba event/trigger identify as soon as new email arrives to one of the 20 shared mail boxes. this is code which will only work for default inbox: Private Sub inboxItems_ItemAdd(ByVal Item As Object) Dim Msg As Outlook.MailItem Dim MessageInfo Dim Result If TypeName(Item) = "MailItem" Then Dim cn As Object Dim sCon As String Dim sSQL As String Dim bytHasAttachment As String Dim strAddress As String Dim objSender, exUser Dim olRecipient As Outlook.Recipient Dim strToEmails, strCcEmails, strBCcEmails As String For Each olRecipient In Item.Recipients Dim mail As String If olRecipient.AddressEntry Is Nothing Then mail = olRecipient.Address ElseIf olRecipient.AddressEntry.GetExchangeUser Is Nothing Then mail = olRecipient.Address Else mail = olRecipient.AddressEntry.GetExchangeUser.PrimarySmtpAddress End If If olRecipient.Type = Outlook.OlMailRecipientType.olTo Then strToEmails = strToEmails + mail & ";" ElseIf olRecipient.Type = Outlook.OlMailRecipientType.olCC Then strCcEmails = strCcEmails + mail & ";" ElseIf olRecipient.Type = Outlook.OlMailRecipientType.olBCC Then strBCcEmails = strBCcEmails + mail & ";" End If Next With Item If Item.Attachments.Count > 0 Then bytHasAttachment = 1 Else bytHasAttachment = 0 End If End With 'On Error Resume Next 'PropertyAccessor can raise an exception if a property is not found If Item.SenderEmailType = "SMTP" Then strAddress = Item.SenderEmailAddress Else 'read PidTagSenderSmtpAddress strAddress = Item.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x5D01001F") If Len(strAddress) = 0 Then Set objSender = Item.Sender If Not (objSender Is Nothing) Then 'read PR_SMTP_ADDRESS_W strAddress = objSender.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001F") If Len(strAddress) = 0 Then Set exUser = objSender.GetExchangeUser If Not (exUser Is Nothing) Then strAddress = exUser.PrimarySmtpAddress End If End If End If End If End If On Error GoTo ErrorHandler Set cn = CreateObject("ADODB.Connection") sCon = "Driver=MySQL ODBC 8.0 ANSI Driver;SERVER=localhost;UID=root;PWD={Platinum#123};DATABASE=liva_dev_gm;PORT=3306;COLUMN_SIZE_S32=1;DFLT_BIGINT_BIND_STR=1" cn.Open sCon sSQL = "INSERT INTO tbl_gmna_emailmaster_inbox (eMail_Icon, eMail_MessageID, eMail_Folder, eMail_Act_Subject, eMail_From, eMail_TO, eMail_CC, " & _ "eMail_BCC, eMail_Body, eMail_DateReceived, eMail_TimeReceived, eMail_Anti_Post_Meridiem, eMail_Importance, eMail_HasAttachment) " & _ "VALUES (""" & Item.MessageClass & """, " & _ """" & Item.EntryID & """, " & _ """Inbox""" & ", " & _ """" & Item.Subject & """, " & _ """" & strAddress & """, " & _ """" & strToEmails & """, " & _ """" & strCcEmails & """, " & _ """" & strBCcEmails & """, " & _ """" & Item.Body & """, " & "'" & Format(Item.ReceivedTime, "YYYY-MM-DD") & "', " & "'" & Format(Item.ReceivedTime, "hh:mm:ss") & "', " & "'" & Format(Item.ReceivedTime, "am/pm") & "', " & "'" & Item.Importance & "', " & "'" & bytHasAttachment & "')" cn.Execute sSQL End If ExitNewItem: bytHasAttachment = "" Exit Sub ErrorHandler: MsgBox Err.Number & " - " & Err.Description Resume ExitNewItem End Sub
If the 20 shared mailboxes are in the navigation pane. Option Explicit Private WithEvents inboxItms As Items Private WithEvents sharedInboxItms1 As Items ' ... Private WithEvents sharedInboxItms20 As Items Private Sub Application_Startup() Dim defaultInbox As Folder Dim sharedMailbox1 As Folder Dim sharedInbox1 As Folder ' ... Dim sharedMailbox20 As Folder Dim sharedInbox20 As Folder Set defaultInbox = Session.GetDefaultFolder(olFolderInbox) Set inboxItms = defaultInbox.Items Set sharedMailbox1 = Session.Folders("SharedMailbox1#somewhere.com") Set sharedInbox1 = sharedMailbox1.Folders("Inbox") ' typo fixed 'Set sharedInboxItms1 = sharedInbox1.Folders("Inbox").Items Set sharedInboxItms1 = sharedInbox1.Items ' ... Set sharedMailbox20 = Session.Folders("SharedMailbox20#somewhere.com") Set sharedInbox20 = sharedMailbox20.Folders("Inbox") ' typo fixed 'Set sharedInboxItms20 = sharedInbox20.Folders("Inbox").Items Set sharedInboxItms20 = sharedInbox20.Items End Sub Private Sub inboxItms_ItemAdd(ByVal Item As Object) ' current code for default inbox End Sub Private Sub sharedInboxItms1_ItemAdd(ByVal Item As Object) inboxItms_ItemAdd Item End Sub ' ... Private Sub sharedInboxItms20_ItemAdd(ByVal Item As Object) inboxItms_ItemAdd Item End Sub
Search by Email address with urn:schemas
I found this code from Ricardo Diaz. It runs through. I would like to search for the latest email I received or sent to a specific email address as opposed to search by subject. I replaced searchString = "urn:schemas:httpmail:subject like '" & emailSubject & "'" with searchString = "urn:schemas:httpmail:to like '" & emailSubject & "'" The search returns an empty object. What is the urn:schemas to search for the email address of the sender and receiver in my Outlook Inbox and Sent Items? This is the code I am trying to run: In a VBA module: Public Sub ProcessEmails() Dim testOutlook As Object Dim oOutlook As clsOutlook Dim searchRange As Range Dim subjectCell As Range Dim searchFolderName As String ' Start outlook if it isn't opened (credits: https://stackoverflow.com/questions/33328314/how-to-open-outlook-with-vba) On Error Resume Next Set testOutlook = GetObject(, "Outlook.Application") On Error GoTo 0 If testOutlook Is Nothing Then Shell ("OUTLOOK") End If ' Initialize Outlook class Set oOutlook = New clsOutlook ' Get the outlook inbox and sent items folders path (check the scope specification here: https://learn.microsoft.com/en-us/office/vba/api/outlook.application.advancedsearch) searchFolderName = "'" & Outlook.Session.GetDefaultFolder(olFolderInbox).FolderPath & "','" & Outlook.Session.GetDefaultFolder(olFolderSentMail).FolderPath & "'" ' Loop through excel cells with subjects Set searchRange = ThisWorkbook.Worksheets("Sheet1").Range("A2:A4") For Each subjectCell In searchRange ' Only to cells with actual subjects If subjectCell.Value <> vbNullString Then Call oOutlook.SearchAndReply(subjectCell.Value, searchFolderName, False) End If Next subjectCell MsgBox "Search and reply completed" ' Clean object Set testOutlook = Nothing End Sub In a class module named clsOutlook: Option Explicit ' Credits: Based on this answer: https://stackoverflow.com/questions/31909315/advanced-search-complete-event-not-firing-in-vba ' Event handler for outlook Dim WithEvents OutlookApp As Outlook.Application Dim outlookSearch As Outlook.Search Dim outlookResults As Outlook.Results Dim searchComplete As Boolean ' Handler for Advanced search complete Private Sub outlookApp_AdvancedSearchComplete(ByVal SearchObject As Search) 'MsgBox "The AdvancedSearchComplete Event fired." searchComplete = True End Sub Sub SearchAndReply(emailSubject As String, searchFolderName As String, searchSubFolders As Boolean) ' Declare objects variables Dim customMailItem As Outlook.MailItem Dim searchString As String Dim resultItem As Integer ' Variable defined at the class level Set OutlookApp = New Outlook.Application ' Variable defined at the class level (modified by outlookApp_AdvancedSearchComplete when search is completed) searchComplete = False ' You can look up on the internet for urn:schemas strings to make custom searches searchString = "urn:schemas:httpmail:to like '" & emailSubject & "'" ' Perform advanced search Set outlookSearch = OutlookApp.AdvancedSearch(searchFolderName, searchString, searchSubFolders, "SearchTag") ' Wait until search is complete based on outlookApp_AdvancedSearchComplete event While searchComplete = False DoEvents Wend ' Get the results Set outlookResults = outlookSearch.Results If outlookResults.Count = 0 Then Exit Sub ' Sort descending so you get the latest outlookResults.Sort "[SentOn]", True ' Reply only to the latest one resultItem = 1 ' Some properties you can check from the email item for debugging purposes On Error Resume Next Debug.Print outlookResults.Item(resultItem).SentOn, outlookResults.Item(resultItem).ReceivedTime, outlookResults.Item(resultItem).SenderName, outlookResults.Item(resultItem).Subject On Error GoTo 0 Set customMailItem = outlookResults.Item(resultItem).ReplyAll ' At least one reply setting is required in order to replyall to fire customMailItem.Body = "Just a reply text " & customMailItem.Body customMailItem.Display End Sub The cells A2:A4 in Sheet1 contain email address such as rainer#gmail.com for instance.
You can get to what appears to be "urn:schemas:httpmail:to" another way. Read MAPI properties not exposed in Outlook's Object Model The usefulness is still to be proven as the values from the the address-related properties are either not available or trivial. Option Explicit ' https://www.slipstick.com/developer/read-mapi-properties-exposed-outlooks-object-model/ Const PR_RECEIVED_BY_NAME As String = "http://schemas.microsoft.com/mapi/proptag/0x0040001E" Const PR_SENT_REPRESENTING_NAME As String = "http://schemas.microsoft.com/mapi/proptag/0x0042001E" Const PR_RECEIVED_BY_EMAIL_ADDRESS As String = "http://schemas.microsoft.com/mapi/proptag/0x0076001E" Const PR_SENT_REPRESENTING_EMAIL_ADDRESS As String = "http://schemas.microsoft.com/mapi/proptag/0x0065001E" Const PR_SENDER_EMAIL_ADDRESS As String = "http://schemas.microsoft.com/mapi/proptag/0x0C1F001E" Sub ShowPropertyAccessorValue() Dim oItem As Object Dim propertyAccessor As outlook.propertyAccessor ' for testing ' select an item from any folder not the Sent folder ' then an item from the Sent folder Set oItem = ActiveExplorer.Selection.item(1) If oItem.Class = olMail Then Set propertyAccessor = oItem.propertyAccessor Debug.Print Debug.Print "oItem.Parent......................: " & oItem.Parent Debug.Print "Sender Display name...............: " & oItem.Sender Debug.Print "Sender address....................: " & oItem.SenderEmailAddress Debug.Print "PR_RECEIVED_BY_NAME...............: " & _ propertyAccessor.GetProperty(PR_RECEIVED_BY_NAME) Debug.Print "PR_SENT_REPRESENTING_NAME.........: " & _ propertyAccessor.GetProperty(PR_SENT_REPRESENTING_NAME) Debug.Print "PR_RECEIVED_BY_EMAIL_ADDRESS......: " & _ propertyAccessor.GetProperty(PR_RECEIVED_BY_EMAIL_ADDRESS) Debug.Print "PR_SENT_REPRESENTING_EMAIL_ADDRESS: " & _ propertyAccessor.GetProperty(PR_SENT_REPRESENTING_EMAIL_ADDRESS) Debug.Print "PR_SENDER_EMAIL_ADDRESS...........: " & _ propertyAccessor.GetProperty(PR_SENDER_EMAIL_ADDRESS) End If End Sub Example format from Filtering Items Using a String Comparison Private Sub RestrictBySchema() Dim myInbox As Folder Dim myFolder As Folder Dim propertyAccessor As propertyAccessor Dim strFilter As String Dim myResults As Items Dim mailAddress As String ' for testing ' open any folder not the Sent folder ' then the Sent folder Set myFolder = ActiveExplorer.CurrentFolder Debug.Print "myFolder............: " & myFolder Debug.Print "myFolder.items.Count: " & myFolder.Items.Count mailAddress = "email#somewhere.com" Debug.Print "mailAddress: " & mailAddress ' Filtering Items Using a String Comparison ' https://learn.microsoft.com/en-us/office/vba/outlook/how-to/search-and-filter/filtering-items-using-a-string-comparison 'strFilter = "#SQL=""https://schemas.microsoft.com/mapi/proptag/0x0037001f"" = 'the right ""stuff""'" 'Debug.Print "strFilter .....: " & strFilter ' Items where PR_RECEIVED_BY_EMAIL_ADDRESS = specified email address ' This is the To ' No result from the Sent folder ' Logical as the item in the Sent folder could have multiple receivers Debug.Print Debug.Print "PR_RECEIVED_BY_EMAIL_ADDRESS" strFilter = "#SQL=" & """" & PR_RECEIVED_BY_EMAIL_ADDRESS & """" & " = '" & mailAddress & "'" Debug.Print "strFilter .....: " & strFilter Set myResults = myFolder.Items.Restrict(strFilter) Debug.Print " myResults.Count.....: " & myResults.Count ' Items where PR_SENT_REPRESENTING_EMAIL_ADDRESS = specified email address Debug.Print Debug.Print "PR_SENT_REPRESENTING_EMAIL_ADDRESS" strFilter = "#SQL=" & """" & PR_SENT_REPRESENTING_EMAIL_ADDRESS & """" & " = '" & mailAddress & "'" Debug.Print "strFilter .....: " & strFilter Set myResults = myFolder.Items.Restrict(strFilter) Debug.Print " myResults.Count.....: " & myResults.Count ' Items where SenderEmailAddress = specified email address Debug.Print Debug.Print "SenderEmailAddress" strFilter = "[SenderEmailAddress] = '" & mailAddress & "'" Debug.Print "strFilter .....: " & strFilter Set myResults = myFolder.Items.Restrict(strFilter) Debug.Print " myResults.Count.....: " & myResults.Count ' Items where PR_SENDER_EMAIL_ADDRESS = specified email address Debug.Print Debug.Print "PR_SENDER_EMAIL_ADDRESS" strFilter = "#SQL=" & """" & PR_SENDER_EMAIL_ADDRESS & """" & " = '" & mailAddress & "'" Debug.Print "strFilter .....: " & strFilter Set myResults = myFolder.Items.Restrict(strFilter) Debug.Print " myResults.Count.....: " & myResults.Count End Sub
How to return task status from a mail item?
I have a code that returns various properties for mail items. I'm trying to add the "task status" to my report. I get a run-time error '438' "Object doesn't support this property or method". I'm trying to extract whether the little flag in Outlook is completed (aka checked). Here is what I have so far: For Each currentTask In currentItem.Tasks Debug.Print currentTask.Status Report = Report & currentTask.Status Next It is part of this larger sub: Private Sub GetAllEmailsInFolder(CurrentFolder As Outlook.Folder, Report As String) Dim currentItem Dim attachment As attachment Dim currentMail As MailItem Dim currenTask As TaskItem Report = Report & "Folder Name: " & CurrentFolder.Name & " (Store: " & CurrentFolder.Store.DisplayName & ")" & " (Date of report: " _ & Date & ")" & vbCrLf & "Subject Name|Categories|Attachment Count|Task Status|Attachment Name(s)" & vbCrLf For Each currentItem In CurrentFolder.Items Report = Report & currentItem.Subject & "|" Report = Report & currentItem.Categories & "|" Report = Report & currentItem.Attachments.Count & "|" 'need help here For Each currentTask In currentItem.Tasks Debug.Print currentTask.Status Report = Report & currentTask.Status Next ' For Each attachment In currentItem.Attachments Debug.Print attachment.FileName Report = Report & attachment.FileName & "," Next Report = Report & vbCrLf Next End Sub
A mailitem has a .FlagStatus property. Option Explicit ' Consider this mandatory ' Tools | Options | Editor tab ' Require Variable Declaration ' If desperate declare as Variant Private Sub GetAllEmailsInFolder(CurrentFolder As outlook.Folder, Report As String) ' Code for flags not reliable in IMAP accounts Dim currentItem As Object Dim attachment As attachment Dim currentMail As MailItem 'Dim currenTask As TaskItem ' <--- missing Option Explicit? Dim currentTask As TaskItem Dim currentFolderItems As Items Report = Report & "Folder Name: " & CurrentFolder.Name & " (Store: " & CurrentFolder.Store.DisplayName & ")" & " (Date of report: " _ & Date & ")" & vbCrLf & "Subject Name|Categories|Attachment Count|Task Status|Attachment Name(s)" & vbCrLf Set currentFolderItems = CurrentFolder.Items For Each currentItem In currentFolderItems If currentItem.Class = olMail Then Set currentMail = currentItem With currentMail Debug.Print .Subject Report = Report & .Subject & "|" Report = Report & .categories & "|" Report = Report & .Attachments.Count & "|" ' No longer in current documentation ' https://learn.microsoft.com/en-us/previous-versions/office/developer/office-2010/bb644164(v=office.14) ' Still could be good for decades ' 0 - olNoFlag ' 1 - olFlagComplete ' 2 - olFlagMarked Debug.Print ".FlagStatus.: " & .FlagStatus 'https://learn.microsoft.com/en-us/office/vba/api/outlook.mailitem.flagrequest Debug.Print ".FlagRequest: " & .FlagRequest Report = Report & .FlagStatus For Each attachment In .Attachments Debug.Print attachment.Filename Report = Report & attachment.Filename & "," Next End With Report = Report & vbCrLf Debug.Print Report ElseIf currentItem.Class = olTask Then Set currentTask = currentItem With currentTask Report = Report & .Subject & "|" Report = Report & .categories & "|" Report = Report & .Attachments.Count & "|" Debug.Print ".Status.....: " & .Status Report = Report & .Status For Each attachment In .Attachments Debug.Print attachment.Filename Report = Report & attachment.Filename & "," Next End With Report = Report & vbCrLf Debug.Print Report Else Debug.Print "neither a mailitem nor a taskitem" End If Set currentItem = Nothing Set currentTask = Nothing Set currentMail = Nothing Next End Sub Private Sub test() Dim currFolder As Folder Dim reportStr As String Set currFolder = ActiveExplorer.CurrentFolder reportStr = "FlagStaus on mailitems: " GetAllEmailsInFolder currFolder, reportStr End Sub
Use MailItem.FlagDueBy / FlagIcon / FlagRequest / FlagStatus / IsMarkedAsTask / TaskCompletedDate / TaskDueDate / TaskStartDate / TaskSubject / ToDoTaskOrdinal properties.
In absence of a better solution, you can use the PropertyAccessor for this purpose. I cannot provide you with a code snippet right now, but you have ilustrative examples on the reference page [1]. The property tag that you are looking for is 0x8025, with DASL http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/81010003. You can use OutlookSpy to determine property tags of actual properties (thanks to [2] for this tip). [1] https://learn.microsoft.com/en-us/office/vba/api/outlook.propertyaccessor [2] How can I get task-specific properties from a MailItem Edit 1 Private Function GetStatus(objItem As Object) As OlTaskStatus Dim oPA As Outlook.PropertyAccessor ' MAPI-level access required to get the "status" property of a Mail Item object. Set oPA = objItem.PropertyAccessor GetStatus = oPA.GetProperty("http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/81010003") Set oPA = Nothing End Function
Process selection rather than entire folder
In reference to the below code, what I am looking to do is rather than process an entire folder I would like only to process the emails that I selected. Otherwise it works perfectly. Jeff Requires the following references: Visual Basic for Applications Microsoft Outlook 14.0 Object Library OLE Automation Microsoft Office 14.0 Object Library Microsoft Shell Controls and Automation Public Sub SaveOLFolderAttachments() ' Ask the user to select a file system folder for saving the attachments Dim oShell As Object Set oShell = CreateObject("Shell.Application") Dim fsSaveFolder As Object Set fsSaveFolder = oShell.BrowseForFolder(0, "Please Select a Save Folder:", 1) If fsSaveFolder Is Nothing Then Exit Sub ' Note: BrowseForFolder doesn't add a trailing slash ' Ask the user to select an Outlook folder to process Dim olPurgeFolder As Outlook.MAPIFolder Set olPurgeFolder = Outlook.GetNamespace("MAPI").PickFolder If olPurgeFolder Is Nothing Then Exit Sub ' Iteration variables Dim msg As Outlook.MailItem Dim att As Outlook.Attachment Dim sSavePathFS As String Dim sDelAtts As String For Each msg In olPurgeFolder.Items sDelAtts = "" ' We check each msg for attachments as opposed to using .Restrict("[Attachment] > 0") ' on our olPurgeFolder.Items collection. The collection returned by the Restrict method ' will be dynamically updated each time we remove an attachment. Each update will ' reindex the collection. As a result, it does not provide a reliable means for iteration. ' This is why the For Each style loops will not work. ~~ If msg.Attachments.Count > 0 Then ' This While loop is controlled via the .Delete method which ' will decrement msg.Attachments.Count by one each time. ~~ While msg.Attachments.Count > 0 ' Save the attachment to the file system sSavePathFS = fsSaveFolder.Self.path & "\" & msg.Attachments(1).filename msg.Attachments(1).SaveAsFile sSavePathFS ' Build up a string to denote the file system save path(s) ' Format the string according to the msg.BodyFormat. If msg.BodyFormat <> olFormatHTML Then sDelAtts = sDelAtts & vbCrLf & "<file://" & sSavePathFS & ">" Else sDelAtts = sDelAtts & "<br>" & "<a href='file://" & sSavePathFS & "'>" & sSavePathFS & "</a>" End If ' Delete the current attachment. We use a "1" here instead of an "i" ' because the .Delete method will shrink the size of the msg.Attachments ' collection for us. Use some well placed Debug.Print statements to see ' the behavior. ~~ msg.Attachments(1).Delete Wend ' Modify the body of the msg to show the file system location of ' the deleted attachments. If msg.BodyFormat <> olFormatHTML Then msg.Body = msg.Body & vbCrLf & vbCrLf & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts Else msg.HTMLBody = msg.HTMLBody & "<p></p><p>" & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts & "</p>" End If ' Save the edits to the msg. If you forget this line, the attachments will not be deleted. ~~ msg.Save End If Next End Sub
Drop the pickfolder code and select the items first. ' http://msdn.microsoft.com/en-us/library/office/aa171941(v=office.11).aspx Untested code Sub SaveOLSelectedItemsAttachments() Dim myOlExp As Outlook.Explorer Dim myOlSel As Outlook.Selection Dim x As Integer Set myOlExp = Application.ActiveExplorer Set myOlSel = myOlExp.Selection ' Ask the user to select a file system folder for saving the attachments Dim oShell As Object Set oShell = CreateObject("Shell.Application") Dim fsSaveFolder As Object Set fsSaveFolder = oShell.BrowseForFolder(0, "Please Select a Save Folder:", 1) If fsSaveFolder Is Nothing Then Exit Sub ' Note: BrowseForFolder doesn't add a trailing slash For x = 1 To myOlSel.Count ' Iteration variables Dim att As Outlook.Attachment Dim sSavePathFS As String Dim sDelAtts As String Dim msg as mailitem Set msg = myOlSel.Item(x) sDelAtts = "" ' We check the item for attachments. ' The collection returned by the Restrict method ' will be dynamically updated each time we remove an attachment. Each update will ' reindex the collection. As a result, it does not provide a reliable means for iteration. ' This is why the For Each style loops will not work. ~~ If msg.Attachments.Count > 0 Then ' This While loop is controlled via the .Delete method which ' will decrement msg.Attachments.Count by one each time. ~~ While msg.Attachments.Count > 0 ' Save the attachment to the file system sSavePathFS = fsSaveFolder.Self.path & "\" & msg.Attachments(1).filename msg.Attachments(1).SaveAsFile sSavePathFS ' Build up a string to denote the file system save path(s) ' Format the string according to the msg.BodyFormat. If msg.BodyFormat <> olFormatHTML Then sDelAtts = sDelAtts & vbCrLf & "<file://" & sSavePathFS & ">" Else sDelAtts = sDelAtts & "<br>" & "<a href='file://" & sSavePathFS & "'>" & sSavePathFS & "</a>" End If ' Delete the current attachment. We use a "1" here instead of an "i" ' because the .Delete method will shrink the size of the msg.Attachments ' collection for us. Use some well placed Debug.Print statements to see ' the behavior. ~~ msg.Attachments(1).Delete Wend ' Modify the body of the msg to show the file system location of ' the deleted attachments. If msg.BodyFormat <> olFormatHTML Then msg.Body = msg.Body & vbCrLf & vbCrLf & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts Else msg.HTMLBody = msg.HTMLBody & "<p></p><p>" & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts & "</p>" End If ' Save the edits to the msg. If you forget this line, the attachments will not be deleted. ~~ msg.Save End If Next Next x End Sub
Macro to move selected outlook emails
I have the following macro for saving attachments, it works fine but I would like it to move the emails to another folder once the attachments have been saved. Any help would be much appreciated! Option Explicit Public Sub SaveFolderAttachments() ' Ask the user to select a file system folder for saving the attachments Dim oShell As Object Set oShell = CreateObject("Shell.Application") Dim fsSaveFolder As Object Set fsSaveFolder = oShell.BrowseForFolder(0, "Please Select a Save Folder:", 1) If fsSaveFolder Is Nothing Then Exit Sub ' Note: BrowseForFolder doesn't add a trailing slash ' Ask the user to select an Outlook folder to process Dim olPurgeFolder As Outlook.MAPIFolder Set olPurgeFolder = Outlook.GetNamespace("MAPI").PickFolder If olPurgeFolder Is Nothing Then Exit Sub ' Iteration variables Dim msg As Outlook.MailItem Dim att As Outlook.Attachment Dim sSavePathFS As String Dim sDelAtts For Each msg In olPurgeFolder.Items sDelAtts = "" ' We check each msg for attachments as opposed to using .Restrict("[Attachment] > 0") ' on our olPurgeFolder.Items collection. The collection returned by the Restrict method ' will be dynamically updated each time we remove an attachment. Each update will ' reindex the collection. As a result, it does not provide a reliable means for iteration. ' This is why the For Each loops will not work. If msg.Attachments.Count > 0 Then ' This While loop is controlled via the .Delete method ' which will decrement msg.Attachments.Count by one each time. While msg.Attachments.Count > 0 ' Save the file sSavePathFS = fsSaveFolder.Self.Path & "\" & msg.Attachments(1).FileName msg.Attachments(1).SaveAsFile sSavePathFS ' Build up a string to denote the file system save path(s) ' Format the string according to the msg.BodyFormat. If msg.BodyFormat <> olFormatHTML Then sDelAtts = sDelAtts & vbCrLf & "<file://" & sSavePathFS & ">" Else sDelAtts = sDelAtts & "<br>" & "<a href='file://" & sSavePathFS & "'>" & sSavePathFS & "</a>" End If ' Delete the current attachment. We use a "1" here instead of an "i" ' because the .Delete method will shrink the size of the msg.Attachments ' collection for us. Use some well placed Debug.Print statements to see ' the behavior. msg.Attachments(1).Delete Wend ' Modify the body of the msg to show the file system location of ' the deleted attachments. If msg.BodyFormat <> olFormatHTML Then msg.Body = msg.Body & vbCrLf & vbCrLf & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts msg Else msg.HTMLBody = msg.HTMLBody & "<p></p><p>" & "Attachments Deleted: " & Date & " " & Time & vbCrLf & vbCrLf & "Saved To: " & vbCrLf & sDelAtts & "</p>" End If ' Save the edits to the msg. If you forget this line, the attachments will not be deleted. msg.Save End If Next End Sub
Call MailItem.Move(MAPIFolder) to move a message. Do not use a "for each' loop if youi move the messages (since the collection count changes), use a down loop (for I = Items.Count to 1 step -1) EDIT: Dim objItems as Outlook.Items set objItems = olPurgeFolder.Items for I = objItems.Count to 1 step -1 set msg = objItems.Item(i)