VBA Outlook SendUsingAccount returns Nothing - vba

At our company, we use Outlook Exchange Desktop edition. Some of us have multiple accounts to send/receive emails from. I have created a VBA macro to check for each email when pressing the Send button what account they are sending the mail from, and then to create a handler that checks if this mail arrives in the "Sent items" folder. After arriving it takes this mail and saves it to a predefined folder.
At first I created this macro to only work with the default account and default folder for sent mail. It worked perfectly. Now I added some code to check the account it is sent from in the correct "Sent items" folder (the one of the correct account). Therefore I used the MailItem.SendUsingAccount property.
When applying this macro, 8 out of 10 times, I get the correct account and the macro works fine. The other 2 times, the SendUsingAccount property return "Null" or "Nothing" (I don't know the difference between these two). I found an other thread here where another user suggests the assignment of accounts to Mailitems is not always reliable, but it doesn't state a proper solution to my problem. Why do I sometimes get nothing as a returnvalue and other times it works perfectly fine? When it's not working, it's always about the code line: ZendAcc = Item.SendUsingAccount. Here the ZendAcc variable cannot store the empty SendUsingAccount return.
VBA:
Public WithEvents myOlItems As Outlook.Items
'Sub triggered when pressing the send button in outlook email
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Dim ZendAcc As String
'Checking for multiple accounts
If Application.Session.Accounts.Count > 1 Then
'Check if the itemtype is MailItem. (normally it will always be correct)
If TypeName(Item) <> "MailItem" Then
MsgBox "There is no MailItem"
Exit Sub
Else
'Store AccountName in String
ZendAcc = Item.SendUsingAccount
If ZendAcc = "" Then
Exit Sub
End If
'Create the handler and give it the Accountname String
Call Initialize_handler(ZendAcc)
End If
Else
'When there is only one account, the Accountname doesn't matter, but you need a String
Call Initialize_handler("Useless")
End If
End Sub
Public Sub Initialize_handler(ByVal zendAccount As String)
Dim Store As Store
Dim Folder As Folder
'If there are multiple accounts, check for the right sent mails folder, otherwise use the default one.
If Application.Session.Accounts.Count > 1 Then
For Each oAccount In Application.Session.Accounts
If oAccount.SmtpAddress = zendAccount Then
Set Store = oAccount.DeliveryStore
Set acFolder = Store.GetDefaultFolder(olFolderSentMail)
Exit For
End If
Next
Set myOlItems = acFolder.Items
Else
Set myOlItems = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderSentMail).Items
End If
End Sub
'Catch the added mail and save to folder
Private Sub myOlItems_ItemAdd(ByVal ObjectSent As Object)
'Code to do something with this mail. In my case: store to defined folder.
End Sub

If the account was explicitly set and the message has not been saved first, you might get null (aka Nothing in VB). In that case, assume the very first account from the Application.Session.Accounts collection will be used.

Related

How to run an Outlook VBA only when creating a New e-mail?

I have written an Outlook VBA action to count the words in an e-mail body and place this count in the subject. This runs fine, however I would like this to only run for new e-mails, and not fire when writing a response as a reply, reply all, or forward.
Code runs fine but runs for all types of e-mails: replies, new, forwarding and I need it to only run for new e-mail.
Thank you
The EntryID property is blank for newly created items in Outlook.
Also you may consider handling the Reply, Forward, ReplyAll events of the MailItem class to detect cases when you shouldn't count words.
Maybe like:
Sub GetReplyType()
Dim objItem As Object
Set objItem = Application.ActiveInspector.CurrentItem
If objItem.Class = olMail Then
Select Case objItem.Verb
Case olReply
MsgBox "The current item is a Reply."
Case olReplyAll
MsgBox "The current item is a ReplyAll."
Case olForward
MsgBox "The current item is a Forward."
Case Else
MsgBox "The current item is not a Reply, ReplyAll or Forward."
End Select
End If
End Sub

Create a rule that deletes attachments before forwarding

I have been tasked to create an automated report system where an report from Google Data Studios are uploaded to specific projects (On a site called Basecamp). The reports always include both a report within the body of the e-mail and an attached PDF file. The are sent to a Gmail account (data studios refuse to schedule towards a non-Google account). The filters within Gmail doesnt really work well with the Basecamp system so I use filters to re-route them towards a Outlook account. There I use rules to send each e-mail towards the correct client within Basecamp.
Here comes the problem, Basecamp shows both the body of the e-mail AND the attached PDF version which makes us show duplicates.
Is there a way to create a macro that first deletes all attachments (or body of an e-mail) and THEN forward the e-mail.
It cant be done manually it have to be a rule that does it automaticaly. Keep in mind that I am not a coder and have never done anything like this so please keep it simple for my dumb brain!
Thank you in advance!
Marcus
PS: I found a code that seems to be what I am after.
Public WithEvents ReceivedItems As Outlook.Items
Private Sub Application_Startup()
Set ReceivedItems = Outlook.Application.Session.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub ReceivedItems_ItemAdd(ByVal Item As Object)
Dim xForwardMail As Outlook.MailItem
Dim xEmail As MailItem
On Error Resume Next
If Item.Class <> olMail Then Exit Sub
Set xEmail = Item
If InStrRev(UCase(xEmail.Subject), UCase("kto feature")) = 0 Then Exit Sub 'change subject text to your need
If xEmail.Attachments.Count = 0 Then Exit Sub
Set xForwardMail = xEmail.Forward
With xForwardMail
.HTMLBody = ""
With .Recipients
.Add "skyyang#addin88.com" 'change address to your own
.ResolveAll
End With
.Send
End With
End Sub
I am trying to get that code to work, and changes the subject to a specific word and then route it to a final e-mail account that then filters out to correct clients. However the code doesnt seem to work, it DOES forward the e-mail but the attachment is still there. The code was found at https://www.extendoffice.com/documents/outlook/5359-outlook-forward-attachment-only.html#a1
It seems you need to modify the code slightly:
Public WithEvents ReceivedItems As Outlook.Items
Private Sub Application_Startup()
Set ReceivedItems = Outlook.Application.Session.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub ReceivedItems_ItemAdd(ByVal Item As Object)
Dim xForwardMail As Outlook.MailItem
Dim xEmail As MailItem
Dim myattachments as Outlook.Attachments
On Error Resume Next
If Item.Class <> olMail Then Exit Sub
Set xEmail = Item
If InStrRev(UCase(xEmail.Subject), UCase("kto feature")) = 0 Then Exit Sub 'change subject text to your need
If xEmail.Attachments.Count = 0 Then Exit Sub
Set xForwardMail = xEmail.Forward
Set myattachments = xForwardMail.Attachments
While myattachments.Count > 0
myattachments.Remove 1
Wend
With xForwardMail
.HTMLBody = ""
With .Recipients
.Add "skyyang#addin88.com" 'change address to your own
.ResolveAll
End With
.Send
End With
End Sub
The Remove method of the Attachments class removes an object from the collection.

Detect whether an email is currently being edited in Outlook?

I have a macro that runs on the Application_NewMail event - but I've seen it have weird impacts if the user is currently composing an email or reply - sometimes crashing outlook and losing their progress.
Is there a way that I can detect whether the user is currently composing an email?
This would allow me to cancel the macro and avoid interrupting the user.
I was able to find bits and pieces from related questions, but nothing that took into account both the pop-up email editor and the inline-response. Here's the solution I pulled together (which seems to cover all bases):
Private Function IsUserEditing() As Boolean
' Check if the user is composing an email. Don't interrupt them if we are.
' 1. Check if the user has the pop-up email 'inspector' window open
If Not (Application.ActiveInspector Is Nothing) Then
Dim OpenWindow As Variant
Set OpenWindow = Application.ActiveInspector.CurrentItem
If TypeOf OpenWindow Is MailItem Then
Dim NewMail As MailItem
Set NewMail = OpenWindow
' Check if the mail they're viewing is not 'Sent' (i.e. being edited)
If Not (NewMail.Sent) Then
IsUserEditing = True
Exit Function
End If
End If
' 2. Check if the user is replying to an email using the 'inline response' feature
ElseIf Not (Application.ActiveExplorer.ActiveInlineResponse Is Nothing) Then
IsUserEditing = True
Exit Function
End If
IsUserEditing = False
End Function
It can be used like this:
Private Sub Application_NewMail()
Debug.Print "New mail received..."
' Check if the user is composing an email. Don't interrupt them if we are.
If IsUserEditing Then
Debug.Print "User appears to be composing an email. Cancelling..."
Exit Sub
End If
' Otherwise Proceed
PerformOnNewMailActions
End Sub
Hope this helps others!

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

In outlook vba how can I find what datetime a mail item was read?

I would like to be able to find out what time/date emails received in microsoft outlook were read.
I can't see that the information is saved by Outlook. Nor does it appear the LastModificationTime reflects this either - it isn't updated when an item is marked as read (at least in Outlook 2007)
Assuming this is correct, I have decided to store this information in the future, and created a userproperty to reflect this. I've hooked the mailitem.PropertyChange event handler with the following code, but it's not a very universal solution - I'll have to put the code into every Outlook app I use. Is there a more efficient way of doing it?
This code in placed in the ThisOutlookSession module (and Outlook restarted)
Private WithEvents objExplorer As Outlook.Explorer
Private WithEvents myItem As Outlook.MailItem
Private Sub Application_Startup()
Set objExplorer = Application.ActiveExplorer
End Sub
Private Sub objExplorer_SelectionChange()
If objExplorer.CurrentFolder.DefaultItemType = olMailItem Then
If objExplorer.Selection.count > 0 Then
Set myItem = objExplorer.Selection(1)
End If
End If
End Sub
Private Sub myItem_PropertyChange(ByVal Name As String)
' Debug.Print Name & "=" & myItem.UnRead
If Name = "UnRead" And myItem.UnRead = False Then
Dim myProperty As Outlook.UserProperty
Set myProperty = myItem.UserProperties("ReadTime")
If (myProperty Is Nothing) Then Set myProperty = myItem.UserProperties.Add("ReadTime", olNumber)
myProperty.Value = Now()
myItem.Save
' Debug.Print Format(myItem.UserProperties("ReadTime"), "hh:mm:ss dd/mm/yy")
ElseIf Name = "UnRead" And myItem.UnRead = True Then
myItem.UserProperties("ReadTime").Delete
End If
' Debug.Print
End Sub
Thanks
You are right, the Outlook object model doesn't provide anything about the Read status (the time when it was marked as read).
VBA is not designed for distributing on multiple PCs. You need to develop an Outlook add-in instead. That's exactly for what they were introduced. See Walkthrough: Creating Your First Application-Level Add-in for Outlook to get started.
You cannot do that - strictly speaking, read/unread state is not even part of the message: it is stored separately. And Exchange Public Folders store stores that state on the per-user basis.
If you set the user property, it will not be persisted unless you call Save, but that will change the last modification time.