Outlook Rules - Alternative to wildcards - vba

Every day I get one or more spam emails of a very specific type to my xxxxx#gmail account.
They all have a garbled and unique FROM: email address.
The TO: and CC: fields are always of the form xxxxx[random chars]#aol.com. For example I got one today with
TO: xxxxx#aol.com
CC: xxxxxY7#aol.com
I would like to create a rule to automatically send these to spam and block. Two possible conditionals would be:
"with xxxxx AND #aol.com in recipient's address"
"with xxxxx*#aol.com in recipient's address" (with * as a wildcard).
#1 doesnt work because the "specific words" it requests are concatenated with OR, no option to use AND.
#2 doesn't work because as far as I can tell there is no way to use wildcards.
Any suggestions? I realize there probably is a straightforward VBA script solution but I haven't played with VBA in over a decade. Was hoping there might be another clever non-VBA work around. Thanks.

There is no workaround. You can handle incoming emails in Outlook VBA by handling the NewMailEx event of the Application class. This event fires once for every received item that is processed by Microsoft Outlook. The item can be one of several different item types, for example, MailItem, MeetingItem, or SharingItem. The EntryIDsCollection string contains the Entry ID that corresponds to that item. You can use the Entry ID from the EntryIDCollection parameter to call the NameSpace.GetItemFromID method and process the item.
In the NewMailEx event handler you can check the required properties such as Recipients and etc. and move the item wherever you need.

As stated, NewMailEx can be used to handle some filtering like this and I have a regex filter that I use as standard automatic rules don't allow that (or at least not currently - maybe a future upgrade). If you can use a regex to identify the e-mails you want to process then you could use this code:
In ThisOutlookSession
Private Sub Application_NewMailEx(ByVal EntryIDCollection As String)
On Error Resume Next
Call RegExFilterRules(EntryIDCollection)
End Sub
In a module
Sub RegExFilterRules(ItemID As String)
With Application.GetNamespace("MAPI")
Dim Inbox As Outlook.MAPIFolder: Set Inbox = .GetDefaultFolder(olFolderInbox)
Dim Junk As Outlook.MAPIFolder: Set Junk = .GetDefaultFolder(olFolderJunk)
Dim olItem As Outlook.MailItem: Set olItem = .GetItemFromID(ItemID, Inbox.StoreID)
End With
'On Error Resume Next
If Not olItem Is Nothing And olItem.Class = olMail Then
If IsPatternFound(olItem.subject, "^M\d+$") Then olItem.Move Junk 'olItem.Delete 'olItem.UnRead = False
If olItem.Sender = "cortana#microsoft.com" Then olItem.Delete
End If
Set olItem = Nothing
End Sub
Private Function IsPatternFound(Content As String, Pattern As String) As Boolean
' Requires Reference: Microsoft Scripting Runtime
Dim RegEx As Object: Set RegEx = CreateObject("vbscript.RegExp")
With RegEx
.Global = True
.IgnoreCase = True
.MultiLine = True
.Pattern = Pattern
IsPatternFound = .Test(Content)
End With
End Function

Related

Decline Outlook meeting based on recipient being a distribution list?

I get emails from my old team/role about tech bridges during outages that I no longer attend. I would like to auto-decline them IF they are sent to a specific distribution list (that I'm still part of).
I don't know VBA at all but would this work (I modified another script I found and replaced SenderEmailAddress with Recipients). Don't want to run this until someone who actually knows that they are looking at confirms or denies that this will work (on the off chance it does something wild with me emails).
Private Sub Application_NewMailEx(ByVal EntryIDCollection As String)
Dim xEntryIDs
Dim xItem
Dim i As Integer
Dim xMeeting As MeetingItem, xMeetingDeclined As MeetingItem
Dim xAppointmentItem As AppointmentItem
On Error Resume Next
xEntryIDs = Split(EntryIDCollection, ",")
For i = 0 To UBound(xEntryIDs)
Set xItem = Application.Session.GetItemFromID(xEntryIDs(i))
If xItem.Class = olMeetingRequest Then
Set xMeeting = xItem
xMeeting.ReminderSet = False
If VBA.LCase(xMeeting.Recipients) = VBA.LCase("support.bridge#company.com") Then
Set xAppointmentItem = xMeeting.GetAssociatedAppointment(True)
xAppointmentItem.ReminderSet = False
Set xMeetingDeclined = xAppointmentItem.Respond(olMeetingDeclined)
xMeetingDeclined.Body = "Declined"
xMeetingDeclined.Send
xMeeting.Delete
End If
End If
Next
End Sub
You can't compare the Recipients collection with a string in the following way:
If VBA.LCase(xMeeting.Recipients) = VBA.LCase("support.bridge#company.com") Then
The MeetingItem.Recipients property returns a Recipients collection that represents all the recipients for the Outlook item. Use Recipients(index), where index is the name or index number, to return a single Recipient object. The name can be a string representing the display name, the alias, or the full SMTP email address of the recipient.
The Recipient.DisplayType property returns a constant belonging to the OlDisplayType enumeration that describes the nature of the Recipient. It seems you are interested in the olDistList or olPrivateDistList values.
Also I've noticed the following line of code:
xEntryIDs = Split(EntryIDCollection, ",")
The NewMailEx behavior has been changed more than 10 years ago and now it is fired for each Outlook item separately. So, there is no need to split the string in the event handler, the parameter contains only a single entry ID value.

How to Search Specific Subject, find, & reply all?

I need to search the inbox, sent, draft, & outbox, for the latest message containing a particular Subject line & reply all. (Trying to continue the threads of specific emails.)
Posted on other sites, however I have not received any actionable advice.
The Outlook object model provides the Find/FindNext or Restrict methods of the Items class to search for items in a single folder. But the most powerful and reliable search is using the AdvancedSearch method of the Application class.
The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
One of your links leads to the code you are interested in:
Option Explicit
' 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:subject like '" & emailSubject & "'" ' Use: subject like '%" & emailSubject & "%'" if you want to include words see %
' 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
You may find the Getting started with VBA in Office article helpful.

Categorisation of incoming by Regex arouse : Application_NewMail : Byte Val Mismatch

I am working for the VBA Macros of Outlook 2010 to filter and categorize incoming emails into different folders. The rule is mentioned in the target
When it comes to the implementation and testing, it does prompting error messages boxes instead of successful filtering. Would you please tell me what section under default call Application_NewMail shall proceed ?
Target :
extract words within [this Bracket]
Subject : [ABC] --> create inbox folder ABC
Subject : [CMX] --> create inbox folder ABC
Subject : CMX --> create inbox folder CMX
Subject : INC000000156156 --> create inbox folder INC and sub-folder INC000000156156
Programming Language : VBA Macro
Outlook Version : 2010
Here is my code and I have no clue on how to create folders if empty and assign email to the folder :
Private Sub Application_NewMail()
Dim olFld As Outlook.MAPIFolder
Set olFld = Outlook.Session.GetDefaultFolder(olFolderInbox)
olFld.Items.Sort "[ReceivedTime]", False
Dim olMail As Outlook.MailItem
Set olMail = olFld.Items.GetFirst
MyNiftyFilter olMail
End Sub
Private Sub MyNiftyFilter(Item As Outlook.MailItem)
Debug.Print Item
Debug.Print Item.Subject
Dim Matches As Variant
Dim RegExp As New VBScript_RegExp_55.RegExp
Dim Pattern As String
Dim Email_Subject As String
Pattern = "(([\w-\s]*)\s*)"
Email_Subject = Item.Subject
With RegExp
.Global = False
.Pattern = Pattern
.IgnoreCase = True
Set Matches = .Execute(Email_Subject)
End With
If Matches.Count > 0 Then
End If
Set RegExp = Nothing
Set Matches = Nothing
Set Item = Nothing
End Sub
You either use ItemAdd event https://stackoverflow.com/a/58428753/4539709 or fix your NewMail to simply
Private Sub Application_NewMail()
Dim Item As Outlook.MailItem
MyNiftyFilter Item
End Sub
The NewMail event fires when new messages arrive in the Inbox and before client rule processing occurs. If you want to process items that arrive in the Inbox, consider using the ItemAdd event on the collection of items in the Inbox. The ItemAdd event passes a reference to each item that is added to a folder.
You Application_NewMail() sub declares but never initializes the Item variable. Use NewMailEx event instead -it passes the new message entry id, whcih you can use to call Application.Session.GetItemFromID.

How to search recipients of previously sent mail?

I have a code which checks the subject in my e-mail and warns me if I already sent an email with this subject. I experienced it would be smarter if it checks for the email address.
I tried substituting "subject" with "recipients" but without success.
Public Sub Application_ItemSend(ByVal thisItem As Object, Cancel As Boolean)
Dim ns As Outlook.NameSpace
Dim olfolder As MAPIFolder
Dim Item As Object
Set olApp = CreateObject("Outlook.Application")
Set olNs = olApp.GetNamespace("MAPI")
Set firstFolder = olNs.Folders("test#dk.com") ' name of my shared inbox
Set olfolder = firstFolder.Folders("sent items")
' iterate thru emails
For Each Item In olfolder.Items
' check subject content & date difference
If InStr(Item.Subject, thisItem.Recipients) And DateDiff("m", Item.SentOn, Now) < 1 Then
' added this part
If MsgBox("already sent", vbYesNo + vbQuestion + vbMsgBoxSetForeground, "Message Text Warning") = vbNo Then
' cancel the email
Cancel = True
End If
Exit For
End If
Just replace thisItem.Subject to thisItem.To
edit: Nathan_Sav said it all :) .Recipents property returns an array so you can loop through it, might as well use .To which will return all recipients separated by semicolon
edit2: note there are two other properties .cc and .bcc if you use them, whilst .Recipients array include all of them
Firstly, do not ever loop through all items in a folder - this is horribly inefficient. Use Items.Find with a restriction on the Subject and SentOn properties.
For the recipients, you can use a restriction on the To property, but keep in mind that on the MAPI level it translates to a restriction on the PR_DISPLAY_TO property, which may or may not include the actual email address. OOM does not create subrestrictions on the PR_MESSAGE_RECIPIENTS property. If using Redemption (I am its author) is an option, its version of RDOItems.Find/Restrict allows to specify Recipients, To, CC, BCC properties in a query and creates an appropriate restriction on the email address and name of the message recipients.
In the worst case you can restrict on the Subject and SentOn properties using Items.Find/FindNext or Items.Restrict, and then explicitly loop through the Recipients collection of the returned matches.

Track email through the draft/send/sent process

I'd like to follow the status of an email as it passes through the phases and folders of its life cycle, through "Drafts", "Outbox", and "Sent".
Of related interest is the ability to access existing emails to gather property info, such as sent time.
I've started with the included block of code. The Do Loop fails the moment the email is sent, because the variable disconnects from the email.
This causes the runtime error
The item has been moved or deleted.
The error number (Err.Number) is different every time, and I wonder what the design purpose is.
How can I stay connected to sending emails as they move through Drafts, Outbox, and Sent?
I see many mentions of the variable disconnecting from the mailitem, but no solutions that rely on the object hierarchy and avoid late-binding to address the issue. I thought perhaps there would be a GUID or UUID that identifies the message, but as indicated in the documentation, all properties such as EntryID can change if the item is moved, and such properties should not be relied on.
With deeper examination, this makes sense because an email is just a record in a database table. And if you duplicate/delete records between tables, the info might be the same or similar, but the record number probably won't be.
Also, that hits other nails: the same email can be sent multiple times, and also can be copied/pasted into different folders, and even different accounts. Now what's unique or not?
Aside from staying "connected" to a email, what properties or techniques can be used to ID one?
If there's no "proper" way to identify a mailitem as described, about the only thing I can think of is to use an existing or custom field, like the "Tag" property of OCX controls, to insert a UUID. Some companies use this sort of technique by putting a call/order/support number in the subject line to make then easier to track.
Dim outlobj As Outlook.Application
Dim mailobj As Outlook.MailItem
Set outlobj = Outlook.Application
Set mailobj = outlobj.CreateItem(olMailItem)
With mailobj
.Recipients.Add "wonderwoman#hallofjustice.com"
.Subject = "Invisible Jet Scheduled Maintenance Reminder"
.Body = "Your invisible jet need to be polished."
.Attachments.Add zipFilename
.Display
.Send
End With
Do
'next line fails due to email moving through Drafts, Outbox, & Sent
'notably, the VBA runtime Err.Num is different each time
'how do i keep the variable connected to a moving target?
If mailobj.Sent = False Then
Sleep 100
Else
MsgBox "The email has been sent."
'other code
Exit Do
End If
Loop
Create a class and add MailItem as the event enabled property of that class. Handle the events such as Open/Write/Send/Save etc. to have custom control on the e-mail life-cycle. EntryID is the unique property for each mail item.
Be cautious of the fact that Entry Id is only generated after the first save of the item and changes implicitly when user manually moves the item between folders.
Following a is an example to get you started:
Add a class Class1 like this
Option Explicit
Public WithEvents mItem As MailItem
Public id As String
Private Sub mItem_Open(Cancel As Boolean)
MsgBox "Mail item will be displayed."
id = mItem.EntryID
End Sub
Add a module with following code:
Option Explicit
Sub test()
Dim cls As New Class1
Dim id As String
Dim outlobj As Outlook.Application
Dim mailobj As Outlook.MailItem
Set outlobj = Outlook.Application
Set mailobj = outlobj.CreateItem(olMailItem)
Set cls.mItem = mailobj
With mailobj
.Recipients.Add "xx#yy.zz"
.Subject = "Test"
.Body = "Test Content of the e-mail."
.Save
.Display
id = cls.id '/ Store ID for later use.
Debug.Print id
End With
'/ Search that e-mail and display its body contents
Call Retrieve(id)
End Sub
Sub Retrieve(sEntryId As String)
Dim mailobj As Outlook.MailItem
Dim ns As NameSpace
Set ns = GetNamespace("MAPI")
Set mailobj = ns.GetItemFromID(sEntryId)
MsgBox mailobj.Body
End Sub
Run the sub test