I have an Excel UserForm which upon a button click generates an Outlook E-mail and the user manually clicks the send option on the mail.
The system will register the E-mail content in an Excel database (only if the user clicks on the Send Option in Outlook through an Withevents Class).
If the database is not available there is an error message which should prompt the user. The prompt is not showing to the user (covered by the Outlook E-mail) because Excel code is processing and the E-mail sending process will be waiting for it to be done.
Is there any way I can show the message box on top of Outlook or run the code to save to the database but only AFTER the Send option is clicked?
The code in the Userform to fill and display the E-mail in Outlook.
Public itmevt As New CMailItemEvents
Public Outapp As Outlook.Application
Public Outmail As Outlook.MailItem
public subject as string
public body as string
Private Sub SendMail_Click()
Set Outapp = New Outlook.Application
Set Outmail = Outlook.Application.CreateItem(0)
Set itmevt.itm = Outmail
body=userform.text1.text
subject=userform.text2.text
itmevt.itm.Subject = "Some Subject"
With itmevt.itm
.HTMLBody = Body
.Subject = subject
.Display
End With
this is the code for the Class called (CMailItemEvents) to detect the Send Option Click
Option Explicit
Public WithEvents itm As Outlook.MailItem
Private Sub itm_Send(Cancel As Boolean)
EmailsForm.savedetails
End Sub
once the Send Option is clicked the code to save will run
sub savedetails()
--->Open Excel DB
If DB.ReadOnly Then
Msgbox ("Error Message Here") ----> here is the problem, the message shows on excel
--- but the outlook mail is on the front of the screen
exit sub
else
--->Save details to DB
End Sub
I tried to keep the code sample as short and simple as possible.
I was finally able to do it with a workaround, I'm not sure if this is going to help anyone.
I've created another event watcher to detect when the E-mail window is actually closed, and according to that the message will be triggered.
this is the updated Class to detect the Send click & the E-mail deactivation event:
Option Explicit
Public WithEvents itm As Outlook.MailItem
Public WithEvents appv As Inspector ----> this part is added
Public Sent as Boolean
Private Sub itm_Send(Cancel As Boolean)
Sent=True ---> Sending E-mail Check
EmailsForm.ETo = itm.To
EmailsForm.ECC = itm.CC
EmailsForm.savedetails
End Sub
---This Part Is Added---
Private Sub appv_Deactivate()
If Sent = True then ---> Sending E-mail Check To Avoid Triggering the watcher if the E-mail is closed without sending
if EmailsForm.Bool=true then
msgbox ("Error Message Here")
EmailsForm.Book=False
Sent=False
End If
End If
End Sub
when the user click the button on the user form the following code it triggered:
Public itmevt As New CMailItemEvents
Public Outapp As Outlook.Application
Public Outmail As Outlook.MailItem
public subject as string
public body as string
Private Sub SendMail_Click()
Set Outapp = New Outlook.Application
Set Outmail = Outlook.Application.CreateItem(0)
Set itmevt.itm = Outmail
Set itmevt.appv = Outmail.GetInspector ----> this is added to link the E-mail window to the deactivation trigger
body=userform.text1.text
subject=userform.text2.text
itmevt.itm.Subject = "Some Subject"
With itmevt.itm
.HTMLBody = Body
.Subject = subject
.Display
End With
I've added a Boolean to be checked from the call
public Bool as Boolean
sub savedetails()
Bool=false ---> Boolean to be checked by the class
--->Open Excel DB
If DB.ReadOnly Then
Bool=true
exit sub
else
--->Save details to DB
End Sub
I hope the above is clear and can help anyone with similar issue; thank you for your support everyone
I've had to deal with stubborn applications myself. Try hiding the application then showing it before your msgbox.
If DB.ReadOnly Then
Application.Visible = False
Application.Visible = True
MsgBox "Error Message Here"
End If
Probably not the most elegant of solutions - but it usually works.
Related
I want to save the email in my local folder, and I saw this link
https://www.mrexcel.com/forum/excel-questions/361751-vba-saving-email-only-after-send-pushed.html
which basically use the class module to save the email after sending it out.
However the problem is, the email saved is the preview email (email that is being displayed before you send the email) instead of sent email (email in which you cannot edit anything anymore)
Dim cls_OL As New clsOutlook
Public objMail_SentMsg As Object
Public Emailpath As String
Sub SendEmail()
Dim OutMail As Object
Set cls_OL.obj_OL = CreateObject("Outlook.Application")
cls_OL.obj_OL.Session.Logon
Set OutMail = cls_OL.obj_OL.CreateItem(0)
Set objMail_SentMsg = OutMail
Emailpath = "V:\test\emailname.msg"
With OutMail
On Error Resume Next
'Assume this all strings variables are fine
.HTMLBody = strmsgContent1 & strmsgContent2
.to = ToEmail
.CC = CC
.BCC = BCC
.Subject = Subject
.Display
End With
Set OutMail = Nothing
End Sub
Option Explicit
Public WithEvents obj_OL As Outlook.Application
Private Sub obj_OL_ItemSend(ByVal Item As Object, Cancel As Boolean)
objMail_SentMsg.SaveAs Emailpath
Set obj_OL = Nothing
End Sub
It saved the email succesfully but as mentioned, only saved the preview/display email not the sent email.
Thank you so much for your help.
Instead of ItemSend monitor the SentItems folder with ItemAdd.
Do not save objMail_SentMsg, save the item identified by ItemAdd as being added to the folder.
If necessary to differentiate mail not to be saved, set up some unique characteristic in the mail when it is created.
Can I use mailitem.entryID in Excel VBA?
I have a tool using excel where I can send an outlook email to recipients using spreadsheet as the UI to display user data. I need to store the entryID of each of the emails send to the user in the excel table. Can I set in the code (excel vba) mailitem.entryID = worksheet.cells().value ? Will it retrieve the entryID? Can you give me your input regarding this? Thank you for your help.
Dim AppOutlook As Object
Dim MailOutlook As Object
Dim Emailto, ccto, sendfrom As String
Set AppOutlook = CreateObject("Outlook.Application")
Set MailOutlook =AppOutlook.CreateItem(0)
Emailto = worksheet.Cells().Value
ccto = worksheet.Cells().Value
sendfrom = "email"
With OutMail
.SentOnBehalfOfName = sendfrom
.To = Emailto
.CC = ccto
.BCC = ""
.Subject =
.BodyFormat = olFormatHTML
.HTMLBody = "body here"
.Send
This is my code, and I plan to add the code worksheet.cells.value = MailOutlook.entryID at the last line of the code. Is it possible? and where to add the AddItem event?
You can read the EntryID property after the message is sent. You cannot do that before or immediately after sending the message - it will be changed when the message is asynchronously sent and moved to the Sent Item folder. The erliest you can access the entry id in the Sent Items folder is when the Items.ItemAdd event fires in the Sent Items folder.
The mail item may not exist any longer after calling the Send method. It can be moved to the Outbox folder for further processing by the transport provide. Item can be marked for processing by the transport provider, not being yet sent. So, we need to handle the ItemSend event in the code.
If you need to be sure that the mail item was sent for sure I'd recommend handling the ItemAdd event of the Items class (see the corresponding property of the Folder class). For example, when an Outlook item is sent, a sent copy is placed to the Sent Items folder in Outlook. You may handle the ItemAdd event for that folder to be sure that the item was sent for sure. Consider adding a user property before displaying the Outlook item and checking it in the ItemAdd event handler to identify the item uniquely.
Demo code based on your code:
Sub Test3()
Dim AppOutlook As Object
Dim MailOutlook As Object
Dim Emailto, ccto, sendfrom As String
Set AppOutlook = CreateObject("Outlook.Application")
Set MailOutlook = AppOutlook.CreateItem(0)
Emailto = Worksheets("Sheet3").Cells(1, 1).Value
ccto = Worksheets("Sheet3").Cells(2, 1).Value
sendfrom = "test#outlook.com"
With MailOutlook
.SentOnBehalfOfName = sendfrom
.To = Emailto
.CC = ccto
.BCC = ""
.Subject = "Test"
.BodyFormat = olFormatHTML
.HTMLBody = "body here"
'.Display
.Send
End With
End Sub
Some ItemAdd snippet for you reference(The current event is not the right one, we still need to test it):
Option Explicit
Private objNS As Outlook.NameSpace
Private WithEvents objItems As Outlook.Items
‘Private Sub Application_Startup()
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim objWatchFolder As Outlook.Folder
Dim AppOutlook As Object
Set AppOutlook = CreateObject("Outlook.Application")
Set objNS = AppOutlook.GetNamespace("MAPI")
'Set the folder and items to watch:
Set objWatchFolder = objNS.GetDefaultFolder(olFolderInbox)
Set objItems = objWatchFolder.Items
Set objWatchFolder = Nothing
End Sub
Private Sub objItems_ItemAdd(ByVal Item As Object)
' Your code goes here
MsgBox "Message subject: " & Item.Subject & vbcrlf & "Message sender: " & Item.SenderName &" (" & Item.SenderEmailAddress & ")"
Worksheets("Sheet3").Cells(3, 1).Value = Item.EntryID
Set Item = Nothing
End Sub
The MailItem object is part of Outlook's VBA Object library. You can see the documentation for the MailItem object on MSDN here.
To use VBA objects from a different program in Microsoft Office (eg. calling Outlook from Excel, calling Visio from Word, calling Excel from Powerpoint) you first need to make sure you have the right References selected in your Visual Basic Editor (VBE).
How to turn on Outlook references in Excel:
In Excel's VBE, go to Tools > References.
A References - VBAProject box will appear.
Under Available References: scroll down until you reach something like Microsoft Outlook 16.0 Object Library (This will differ depending on the version of Office you are using)
Tick the box and press OK.
Now the Outlook Object references have been enabled, you should be able to call Outlook objects and methods from Excel, including MailItem.
I’m currently working on an access Vba program in order to automatically write mails to people. However we chose to still press ‘Send’ in Outlook manually (in case there are possible issues, so we can control the mail beforehand).
Is there a way to have a link in the other direction, as in, when pressing the Send button in Outlook, getting the email address of the person back in excel? (The goal would be to make a ‘history’ sheet in order to keep track of which mails were actually sent and to whom)
Thank you!
Yes. A simple case is shown below. This is bare bones demonstrating the actions you requested.
Public variable, addressSent, holds the To address. A boolean test on mail sent (by #Rory) tests for the mail item having been sent and calls a function, by #Dwipayan Das, that opens a specified Excel file, and writes the addressSent to cell A1 in sheet1.
You can tinker with this to fit your purposes. E.g. Adapt the function to accept a file name as parameter.....
Taking a note from #ashleedawg's book: remember to include a xlApp.Quit line so Excel is not left hanging.
I believe your question wanted to go from Outlook to Excel so this is the application that you will have created that needs closing.
So in Outlook goes the following code:
Put this in a standard module:
Option Explicit
Public addressSent As String
Dim itmevt As New CMailItemEvents
Public Sub CreateNewMessage()
Dim objMsg As MailItem
Set objMsg = Application.CreateItem(olMailItem)
Set itmevt.itm = objMsg
With objMsg
.Display
.To = "somebody#mail.com"
.Subject = "Blah"
addressSent = .To
.Send
End With
End Sub
Public Function openExcel() As Boolean 'Adapted from #Dwipayan Das
Dim xlApp As Object
Dim sourceWB As Object
Dim sourceWS As Object
Set xlApp = CreateObject("Excel.Application")
With xlApp
.Visible = True
.EnableEvents = False
End With
Dim strFile As String
strFile = "C:\Users\User\Desktop\Delete.xlsb" 'Put your file path.
Set sourceWB = xlApp.Workbooks.Open(strFile, , False, , , , , , , True)
sourceWB.Activate
sourceWB.Worksheets(1).Range("A1") = addressSent
End Function
Then in a class module called CMailItemEvents, code from #Rory, put the following:
Option Explicit
Public WithEvents itm As Outlook.MailItem
Private Sub itm_Close(Cancel As Boolean)
Dim blnSent As Boolean
On Error Resume Next
blnSent = itm.Sent
If Err.Number = 0 Then
Debug.Print "not sent"
Else
openExcel
End If
End Sub
References:
Check to see if an Outlook Email was sent from Excel VBA
How can I use Outlook to send email to multiple recipients in Excel VBA
How to open an excel file in Outlook vba code
Create a new Outlook message using VBA
Run code after item sent
Just a quick 'n dirty function that will run in Excel/Access/Word and returns the email address from the most recent item in the Sent Items folder (no error handling, etc):
Function LastSentEmailAddress() As String
'Requires reference: "Microsoft Outlook xx.x Object Library"
Dim olApp As Outlook.Application, olMail As Object
Set olApp = New Outlook.Application 'create Outlook object
Set olMail = olApp.GetNamespace("MAPI").GetDefaultFolder(olFolderSentMail).Items.GetLast
LastSentEmailAddress = olMail.Recipients(1).PropertyAccessor.GetProperty( _
"http://schemas.microsoft.com/mapi/proptag/0x39FE001E") 'get email addy
olApp.Quit 'close Outlook
End Function
A Note about working with Outlook objects from Excel:
When working with applications such as Excel it's important to make sure the application object is properly .Quit / .Close'd when finished with them, (and to Set all objects to Nothing), otherwise there's a risk of inadvertently having multiple instances running, which can lead to memory leaks, which leads to crashes and potential data loss.
To check if there is an existing instance of Outlook, use this function:
Function IsOutlookOpen()
'returns TRUE if Outlook is running
Dim olApp As Outlook.Application
On Error Resume Next
Set olApp = GetObject(, "Outlook.Application")
On Error GoTo 0
If olApp Is Nothing Then
IsOutlookOpen= False
Else
IsOutlookOpen= True
End If
End Function
(Source: Rob de Bruin)
More Information:
MSDN : Items.GetLast Method (Outlook)
MSDN : Items Object (Outlook)
MSDN : Obtain the E-mail Address of a Recipient
Office.com : How to disable warnings about programmatic access to Outlook
MSDN : Chapter 17: Working with Item Bodies (Book Excerpt)
MSDN : Check or Add an Object Library Reference
Stack Overflow : VBA to search an Outlook 2010 mail in Sent Items from Excel
I am using MS Excel and Outlook 2013. I am trying to automate an Excel spreadsheet that sends 5 emails to a specified address using Outlook.
The trick is I want each message to display one at a time and only move on to the next message when the user either hits Send or closes the message. Here is what I have so far:
Sub Send_Emails()
Dim OutApp As Object: Set OutApp = CreateObject("Outlook.Application")
Dim OutMail As Object: Set OutMail = OutApp.CreateItem(0)
'Send Email
With OutMail
.to = "john.doe#mycompany.com"
.Subject = "This is the Subject"
.Body = "This is message"
.Display
End With
On Error Resume Next:
OutMail = Nothing
OutApp = Nothing
End Sub
Sub Send_Five_Emails()
For i = 1 To 5 'Send email 5 times
Call Send_Emails
Next i
End Sub
The problem with this code is that it displays all 5 message windows at once. Is there a way to make the Close event of one message window trigger the Displaying of the next one, so as to make them appear one at a time?
I appreciate the help.
Use .Display (True)
The expression.Display(Modal) argument is used with all objects except for the Explorer and MAPIFolder objects, True to make the window modal. The default value is False.
See Display Method on MSDN
I am trying to call a procedure stored in Outlooks' 'ThisOutlookSession', from an Excel workbook. Unfortunately the Newer Outlook 2010 app does not have compatibility with the application.run *SubName* between MS office products.
It is not an option to complete an Excel script that sends the email on Outlooks behalf due to security messages on '.send' which requires a manned station. (& unable to change security settings from company policy)
Current workflow...
-User sends me an e-mail with 'command' in subject & attachments
-Event listener finds and successfully runs an Excel routine on attachments with the below headers for listening in Outlook
Private WithEvents Items As Outlook.Items
&
Private Sub Items_ItemAdd(ByVal Item As Object)
-Once processed in Excel, I am trying to get this data automatically returned to sender. (This is where the problem is)
I am using late binding in Excel to create and ready the return e-mail. It is one step before '.send'. I would ideally like to avoid a SendKeys statements because it is not fully reliable if working on other workbooks at the same time.
In Excel...
Sub test()
Dim aOutlook As Object
Dim aEmail As Object
Set aOutlook = CreateObject("Outlook.Application")
Set aEmail = aOutlook.CreateItem(0)
aEmail.To = "My email address"
aEmail.Subject = "Testing"
aEmail.Body = "Testing"
aEmail.display
End Sub
I have been endlessly trying to get Outlook to recognise Excel creating this new email with events listed in the MSDN pages. I think something like the below code is what is needed to identify a new mailitem, but no success using most of the preset declarations under the Outlook.mailItem.
Private WithEvents NewItem As Outlook.mailItem
Please let me know if you have solution or an alternative idea for me to pursue
Solved, if anyone needs this in the future. It bypasses security warnings and does not rely on send-keys.
Excel prepares an email and displays it - when prepared, the 'On event' from Outlook recognises the 'to' address when the mailitem is loaded, and can then take over from there: finishing with a .send statement.
Note, more parameters will need to be added such as a specific code in the item.subject to ensure that its the correct e-mail that's being sent.
In Excel:
Sub test()
Dim aOutlook As Object
Dim aEmail As Object
Set aOutlook = CreateObject("Outlook.Application")
Set aEmail = aOutlook.CreateItem(0)
aEmail.To = "abs#123.com"
aEmail.Subject = "Testing"
aEmail.Body = "testing"
aEmail.Display
End Sub
In Outlook:
Public WithEvents myItem As Outlook.mailItem
Private Sub Application_ItemLoad(ByVal Item As Object)
If (TypeOf Item Is mailItem) Then
Set myItem = Item
End If
End Sub
Private Sub myItem_Open(Cancel As Boolean)
If myItem.To = "abs#123.com" Then
MsgBox "Detected"
myItem.Send
End If
End Sub