I have been trying to automate an excel daily report including tables and chart into an email body.
I manually select and copy the range and special paste it as a picture into the email body.
I have been trying to automate this part and here's my code:
MailSender = DashboardSheet.Range("S16")
MailDistribution = DashboardSheet.Range("S17")
MailSubject = DashboardSheet.Range("S18")
Set objOutlook = CreateObject("Outlook.Application")
Set objOutlookMsg = objOutlook.CreateItem(0)
MakeJPG = CopyRangeToJPG("Dashboard", "A1:O8")
If MakeJPG = "" Then
MsgBox "Something go wrong, we can't create the mail"
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
Exit Sub
End If
On Error Resume Next
With objOutlookMsg
.SentOnBehalfOfName = MailSender
.To = MailDistribution
.CC = MailSender
.Subject = MailSubject
.Attachments.Add (SentFiles_Pathname & TodaySentReport_Name), 1, 0
.HTMLBody = .HTMLBody & "<p>" & MakeJPG & "</p>" _
& "<img src='" & MakeJPG & "'width='750' height='520'>"
.Display
End With
On Error GoTo 0
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
Set objOutlookMsg = Nothing
Set objOutlook = Nothing
MacroBook.Activate
End Sub
Function CopyRangeToJPG(NameWorksheet As String, RangeAddress As String) As String
Dim PictureRange As Range
With ActiveWorkbook
On Error Resume Next
.Worksheets(NameWorksheet).Activate
Set PictureRange = .Worksheets(NameWorksheet).Range(RangeAddress)
If PictureRange Is Nothing Then
MsgBox "Sorry this is not a correct range"
On Error GoTo 0
Exit Function
End If
PictureRange.CopyPicture xlScreen, xlPicture
With .Worksheets(NameWorksheet).ChartObjects.Add(PictureRange.Left, PictureRange.Top, PictureRange.Width, PictureRange.Height)
.Activate
.Chart.Paste
.Chart.Export FileName:="\\server01\DATA\Data Reporting and Dashboard\Dashboard\Daily\" & "NamePicture.jpg", FilterName:="JPG"
End With
.Worksheets(NameWorksheet).ChartObjects(.Worksheets(NameWorksheet).ChartObjects.Count).Delete
End With
CopyRangeToJPG = "\\server01\DATA\Data Reporting and Dashboard\Dashboard\Daily\" & "NamePicture.jpg"
Set PictureRange = Nothing
End Function
I end up having no picture in the body, just an error message saying:
The linked image cannot be displayed. The file may have been moved, renamed, or deleted. Verify that the link points to the correct file and location.
I then realized that I see no file called NamePicture.jpg in the picture folder. I can't find where the problem comes from.
I am able to add the picture manually to the email body with a copy/special paste but it doesn't work with vba. I tried the solutions I have seen on other related topics, but none works. Has anybody encountered that issue?
See: Embed picture in outlook mail body excel vba
So if you can fix your picture export problem:
'...
.Attachments.Add (SentFiles_Pathname & TodaySentReport_Name), 1, 0
.Attachments.Add MakeJPG, 1, 0 '<#####
.HTMLBody = .HTMLBody & "<p>" & MakeJPG & "</p>" _
& "<img src='cid:NamePicture.jpg' width='750' height='520'>"
'...
Related
I've used an example to create code to send emails from Excel (with Outlook), using a "Button" (red in my file).
The code works. There is a pre-selected range of rows [B1:K20], that can be manually modified thanks to the Application.InputBox function.
Sub MAIL()
Dim rng As Range
Dim OutApp As Object
Dim OutMail As Object
Dim StrBodyIn, StrBodyEnd As String
StrBodyIn = "Bonjour," & "<br>" & _
" " & "<br>" & _
"Buongiorno," & "<br>"
StrBodyEnd = " " & "<br>" & _
"Cordialement" & "<br>" & _
" " & "<br>" & _
Range("M2") & "<br>"
Set rng = Nothing
On Error Resume Next
Set rng = Sheets("TEST").Application.InputBox("Range", xTitleId, "B1:K20", Type:=8)
On Error GoTo 0
If rng Is Nothing Then
MsgBox "ATTENZIONE!!!" & _
vbNewLine & "Seleziona un range di celle valido.", vbOKOnly
Exit Sub
End If
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = "email#gmail.com"
.CC = ""
.BCC = ""
.Subject = "SITUATION"
.HTMLBody = "<p style='font-family:calibri;font-size:16'>" & StrBodyIn & RangetoHTML(rng) & "<p style='font-family:calibri;font-size:16'>" & StrBodyEnd
.Display 'or use .Send
End With
On Error GoTo 0
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
I want to add a condition.
The selected range of rows should be copied to the body of the email if the "X" symbol is written in the column "A".
In my example, rows n° 1, 2 and n° 5 should be copied.
The two tasks here are separate so I would code them as such. Here would be my approach. Separate your sub into two logical procedures.
Determine the body range
Send the email with the range
Determine the body range
Link your button to this macro. The macro will take an input and convert it into a single column range (Column B). We will then loop through the selected range and look at Column A to determine if there is an x or not. If an x is present, we will resize the range back to it's original size and add it to a collection of cells (Final).
Once the loop is complete, the macro will then do one of the following:
If the range is empty, it will prompt your message box and end the sub (your email macro is never initiated)
If the range is not empty, we will call your EMAIL macro and pass the range along to it.
Sub EmailRange()
Dim Initial As Range, Final As Range, nCell As Range
On Error Resume Next
Set Initial = Sheets("TEST").Application.InputBox("Range", xTitleId, "B1:K20", Type:=8)
On Error GoTo 0
For Each nCell In Initial.Resize(Initial.Rows.Count, 1)
If nCell.Offset(, -1) = "X" Then
If Not Final Is Nothing Then
Set Final = Union(Final, nCell.Resize(1, Initial.Columns.Count))
Else
Set Final = nCell.Resize(1, Initial.Columns.Count)
End If
End If
Next nCell
If Not Final Is Nothing Then
MAIL Final
Else
MsgBox "ATTENZIONE!!!" & vbNewLine & "Seleziona un range di celle valido.", vbOKOnly
End If
End Sub
Send the email with the range
Notice that the macro now has an input (On first line). If the sub is called, you no longer need to validate anything since this was all done in the original sub!
Sub MAIL(Final as Range)
Dim OutApp As Object, OutMail As Object
Dim StrBodyIn As String, StrBodyEnd As String
StrBodyIn = "Bonjour," & "<br>" & " " & "<br>" & "Buongiorno," & "<br>"
StrBodyEnd = " " & "<br>" & "Cordialement" & "<br>" & " " & "<br>" & Range("M2") & "<br>"
Application.EnableEvents = False
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = "email#gmail.com"
.CC = ""
.BCC = ""
.Subject = "SITUATION"
.HTMLBody = "<p style='font-family:calibri;font-size:16'>" & StrBodyIn & RangetoHTML(Final) & "<p style='font-family:calibri;font-size:16'>" & StrBodyEnd
.Display 'or use .Send
End With
On Error GoTo 0
Set OutMail = Nothing
Set OutApp = Nothing
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
I continue my work starting from the 1st question here:
Excel VBA - Outlook Email - Body created with rows having a particular value
Now i have another problem.
I want to repeat the below MACROs on all the SHEETS of my file.
In particular, how can I repeat this function on different SHEETS by only clicking in 1 button present in all the sheets?
All the sheets have the same structure.
I mean, the table resulting in the email must be implemented by adding the datas in all the sheets.
The data should be copied starting from the 1st sheet, for ex. TEST(1) to the last sheet, TEST(9).
The email generated after this process must be ONLY one.
Determine the body range
Sub EmailRange()
Dim Initial As Range, Final As Range, nCell As Range
On Error Resume Next
Set Initial = Sheets("TEST").Application.InputBox("Range", xTitleId, "B1:K20", Type:=8)
On Error GoTo 0
For Each nCell In Initial.Resize(Initial.Rows.Count, 1)
If nCell.Offset(, -1) = "X" Then
If Not Final Is Nothing Then
Set Final = Union(Final, nCell.Resize(1, Initial.Columns.Count))
Else
Set Final = nCell.Resize(1, Initial.Columns.Count)
End If
End If
Next nCell
If Not Final Is Nothing Then
MAIL Final
Else
MsgBox "ATTENZIONE!!!" & vbNewLine & "Seleziona un range di celle valido.", vbOKOnly
End If
End Sub
Send the email with the range
Sub MAIL(Final as Range)
Dim OutApp As Object, OutMail As Object
Dim StrBodyIn As String, StrBodyEnd As String
StrBodyIn = "Bonjour," & "<br>" & " " & "<br>" & "Buongiorno," & "<br>"
StrBodyEnd = " " & "<br>" & "Cordialement" & "<br>" & " " & "<br>" & Range("M2") & "<br>"
Application.EnableEvents = False
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = "email#gmail.com"
.CC = ""
.BCC = ""
.Subject = "SITUATION"
.HTMLBody = "<p style='font-family:calibri;font-size:16'>" & StrBodyIn & RangetoHTML(Final) & "<p style='font-family:calibri;font-size:16'>" & StrBodyEnd
.Display 'or use .Send
End With
On Error GoTo 0
Set OutMail = Nothing
Set OutApp = Nothing
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
I've tried with something like this, but it does not work:
For I = 1 To Worksheets.Count
Sheets(I).Select
***[...]CODE OF "Determine the body range"***
Next I
Sheets("TEST(I)").Select
I have a table in Excel that I want to send to a distribution list in Outlook with the table in the email body.
Using MVP Ron de Bruin's examples and a few others on here I've got code that keeps some of the table formatting but doesn't copy the cells colour if it is a gradient (please use the images as reference).
Sub DisplayEmailButton_Click()
Mail_Selection_Range_Outlook_Body
End Sub
Sub Mail_Selection_Range_Outlook_Body()
Dim rng As Range
Dim OutApp As Object
Dim OutMail As Object
Set rng = Nothing
On Error Resume Next
Set rng = Sheets("Sheet1").Range("C2:Q18").SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If rng Is Nothing Then
MsgBox "The selection is not a range or the sheet is protected" & _
vbNewLine & "please correct and try again.", vbOKOnly
Exit Sub
End If
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = "Team01"
.CC = ""
.BCC = ""
.Subject = "Daily Statistics"
.HTMLBody = "Please see attached daily statistics." & vbCrLf &
RangetoHTML(rng)
.Display
End With
On Error GoTo 0
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
Function RangetoHTML(rng As Range)
Dim TempFile As String, ddo As Long
TempFile = Environ$("temp") & "\" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
' Temporary publish the rng range to a htm file
ddo = ActiveWorkbook.DisplayDrawingObjects
ActiveWorkbook.DisplayDrawingObjects = xlHide
With ActiveWorkbook.PublishObjects.Add( _
SourceType:=xlSourceRange, _
Filename:=TempFile, _
Sheet:=ActiveSheet.Name, _
Source:=Union(rng, rng).Address, _
HtmlType:=xlHtmlStatic)
.Publish True
.Delete
End With
ActiveWorkbook.DisplayDrawingObjects = ddo
'Read all data from the htm file into RangetoHTML
With
CreateObject("Scripting.FileSystemObject").GetFile(TempFile)
.OpenAsTextStream(1, -2)
RangetoHTML = Replace(.ReadAll, "align=center x:publishsource=", "align=left
x:publishsource=")
.Close
End With
'Delete the htm file we used in this function
Kill TempFile
End Function
As Tim suggested I was expecting way too much from that procedure (Thank you, Tim, for the advice!) so I looked into a workaround. If the range is saved as a picture then it keeps all the formatting and the picture can then easily be attached to an email or displayed in the body of the email.
To save as a picture:
Dim Wb As ThisWorkbook
Dim Ws As Worksheet
Dim Ch As Chart
Set Rng = Ws.Range("A1:G18")
Set Ch = Charts.Add
Ch.Location xlLocationAsObject, "Sheet2"
Set Ch = ActiveChart
ActiveChart.Parent.Name = "StatsTemp"
ActiveSheet.ChartObjects("StatsTemp").Height = Rng.Height
ActiveSheet.ChartObjects("StatsTemp").Width = Rng.Width
Rng.CopyPicture xlScreen, xlBitmap
Ch.Paste
Ch.Export Environ("UserProfile") & "\Desktop" & "\" & Format("TempImage") & ".jpg"
Worksheets("Sheet2").ChartObjects("StatsTemp").Delete
Worksheets("Sheet1").Activate
The above code saves the range as an image "TempImage.JPG" to the users desktop by creating a new chart on sheet 2, pasting the range to the chart then saves the chart as an image and deletes the chart.
To attach the picture to an email in the email body:
Dim StrBody As String
Dim OutApp As Object
Dim OutMail As Object
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
StrBody = "Some text here." & "<br>"
On Error Resume Next
With OutMail
.to = "email address"
.CC = ""
.BCC = ""
.Subject = "Email Subject"
.HTMLBody = StrBody & "<img src = '" & Environ("userProfile") &
"\desktop\TempImage.jpg'>"
.Display
End With
On Error GoTo 0
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
Set OutMail = Nothing
Set OutApp = Nothing
The above code creates an email using Microsoft Outlook which contains the saved image file in the email body and displays the email.
The image can be deleted after using:
Kill Environ("UserProfile") & "\Desktop" & "\TempImage.jpg"
Hopefully, this will be of some use to someone!
Credit to Ron de Bruin Microsoft Office MVP for his WinTips!
I've just started working on macros and have made a pretty decent progress so far.
However, I'm stuck in a place and can't find an answer to it.
I'm using a macro to send emails to specific recipients via outlook. I'm sending multiple excel & pdf attachments in each email.
The code works fantastic! I, nonetheless, need to add a condition wherein an email that doesn't have any EXCEL attachments isn't sent and the outlook create mail item for this specific case only closes automatically.
The rest of the macro should continue for other clients with the excel attachments.
Hoping for someone to help me on this. Following is the code that I'm currently using.
Sub SendEmailWithReview_R()
Dim OutApp As Object
Dim OutMail As Object
Dim X As Long
Lastrow = Sheets(1).Cells(Rows.Count, 1).End(xlUp).Row
For X = 10 To Lastrow
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(olmailitem)
With OutMail
.To = Cells(X, 4)
.CC = Cells(X, 6)
.Subject = Cells(X, 8)
.Body = Cells(1, 8)
strlocation = "C:\Users\HKhan\Desktop\Final Macro\" & Cells(X, 1) & "-OICR.xlsx"
On Error Resume Next
.Attachments.Add (strlocation)
On Error Resume Next
strlocation = "C:\Users\HKhan\Desktop\Final Macro\" & Cells(X, 1) & "-OICLR.xlsx"
On Error Resume Next
.Attachments.Add (strlocation)
On Error Resume Next
strlocation = "C:\Users\HKhan\Desktop\Final Macro\" & "OIC - Bank Details" & ".pdf"
On Error Resume Next
.Attachments.Add (strlocation)
On Error Resume Next
strlocation = "C:\Users\HKhan\Desktop\Final Macro\" & "OICL - Bank Details" & ".pdf"
On Error Resume Next
.Attachments.Add (strlocation)
On Error Resume Next
.Display
'send
End With
Next X
End Sub
So instead of waiting for errors or trying to suppress them better check if the file exists. Therefore you can use a function like this, which returns true if a file exists:
Public Function FileExists(FilePath As String) As Boolean
Dim Path As String
On Error Resume Next
Path = Dir(FilePath)
On Error GoTo 0
If Path <> vbNullString Then FileExists = True
End Function
For adding attachments I recommend to use an array for the file names, so you can easily loop through and attach the files if they exist. Everytime we add an attachment we increase the AttachedFilesCount too.
This way you don't use On Error Resume Next wrong and you don't run into debug issues because of that. So you have a clean solution.
With OutMail
.To = Cells(X, 4)
.CC = Cells(X, 6)
.Subject = Cells(X, 8)
.Body = Cells(1, 8)
Dim FileLocations As Variant
FileLocations = Array("C:\Users\HKhan\Desktop\Final Macro\" & Cells(X, 1) & "-OICR.xlsx", _
"C:\Users\HKhan\Desktop\Final Macro\" & Cells(X, 1) & "-OICLR.xlsx", _
"C:\Users\HKhan\Desktop\Final Macro\" & "OIC - Bank Details" & ".pdf", _
"C:\Users\HKhan\Desktop\Final Macro\" & "OICL - Bank Details" & ".pdf")
Dim AttachedFilesCount As Long
Dim FileLocation As Variant
For Each FileLocation In FileLocations
If FileExists(FileLocation) Then
.Attachments.Add (FileLocation)
AttachedFilesCount = AttachedFilesCount + 1
End If
Next FileLocation
If AttachedFilesCount > 0 Then
.Display 'display or send email
Else
.Close 'close it if no attachments
End If
End With
If you now still need additional error handling on adding the attachments (personally I don't think you need it necessarily) you can implement it like this:
On Error Resume Next 'turn error reporting off
.Attachments.Add (FileLocation) 'the line where an error might possibly occur.
If Err.Number <> 0 Then 'throw a msgbox if there is an error
MsgBox "Could not attach file """ & FileLocation & """ to the email." & vbCrLf & Err.Description, vbExclamation, "Error " & Err.Number, Err.HelpFile, Err.HelpContext
End If
On Error Goto 0 'turn error reporting on again!
To add condition to check if OutMail has Excel attachment, simply replace the following
.Display
'send
With these codes
Dim Atmt As Object
For Each Atmt In OutMail.Attachments
Dim sFileType As String
sFileType = LCase$(Right$(Atmt.fileName, 4)) ' Last 4 Char in Filename
Debug.Print Atmt.fileName
Select Case sFileType
Case ".xls", "xlsx"
.Display
'.send
End Select
Next
The below code runs a macro and creates the jpeg.
How can I change the name of the sheet (ex:"Strategic") for an active sheet. When I copy a sheet the code don't work anymore, because of the reference name.
With OutMail
.SentOnBehalfOfName = "Me#Me.Com"
.Display
.Subject = "Strategic Sales"
.To = "Me#Me.Com"
> Call createJpg("Strategic", "A1:F11", "Quota") '
It's about a code to create an outlook mail objectand embed images
Sub sendMail()
Application.Calculation = xlManual
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Dim TempFilePath As String
'Create a new Microsoft Outlook session
Set appOutlook = CreateObject("outlook.application")
'create a new message
Set Message = appOutlook.CreateItem(olMailItem)
With Message
.Subject = "My mail auto Object"
.HTMLBody = "<span LANG=EN>" _
& "<p class=style2><span LANG=EN><font FACE=Calibri SIZE=3>" _
& "Hello,<br ><br >The weekly dashboard is available " _
& "<br>Find below an overview :<BR>"
'first we create the image as a JPG file
**Call createJpg("Dashboard", "B8:H9", "DashboardFile")**
'we attached the embedded image with a Position at 0 (makes the attachment hidden)
TempFilePath = Environ$("temp") & "\"
.Attachments.Add TempFilePath & "DashboardFile.jpg", olByValue, 0
'Then we add an html <img src=''> link to this image
'Note than you can customize width and height - not mandatory
.HTMLBody = .HTMLBody & "<br><B>WEEKLY REPPORT:</B><br>" _
& "<img src='cid:DashboardFile.jpg'" & "width='814' height='33'><br>" _
& "<br>Best Regards,<br>Ed</font></span>"
.To = "contact1#email.com; contact2#email.com"
.Cc = "contact3#email.com"
.Display
'.Send
End With
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
Application.Calculation = xlCalculationAutomatic
End Sub
You need to create createJpg function which transform a range into a jpg file.
Sub createJpg(Namesheet As String, nameRange As String, nameFile As String)
ThisWorkbook.Activate
Worksheets(Namesheet).Activate
Set Plage = ThisWorkbook.Worksheets(Namesheet).Range(nameRange)
Plage.CopyPicture
With ThisWorkbook.Worksheets(Namesheet).ChartObjects.Add(Plage.Left,
Plage.Top, Plage.Width, Plage.Height)
.Activate
.Chart.Paste
.Chart.Export Environ$("temp") & "\" & nameFile & ".jpg", "JPG"
End With
Worksheets(Namesheet).ChartObjects(Worksheets(Namesheet).ChartObjects.Count).Delete
Set Plage = Nothing
End Sub