Insert Looped Text Pulled from Excel into Email Body - excel

#Niton solved my first question for me, which was how to pull in data from an Excel file in a way that would loop through until a new email address was found. It allows me to take data from multiple lines (and a couple fields on those lines) and place it into an Outlook email.
My problem now is that when it does so, I need it to be included in the body of an email. So there would be some text such as a greeting, then 'you have these vouchers that we need paid off, please...EXCEL DATA HERE...Thank you for looking at this, here is the address you can send to, and if you need to update us, email us back'. That wording is not complete and will be changed, but that is the general idea...getting the Excel text into the body of the email. I have added some fields that are pulled to the strVoucher as shown in the code.
I have tried different iterations as at first the Excel info would just repeat along with the text over and over. I then was able to separate at least part of the email code so that it would put in the first greeting piece of text, but then I am stuck in trying to get it to add more text after the Excel data without repeating all the text over and over. I tried to add another 'With Outmail' section after the strVoucher piece is added, but that just overrode the whole email.
Here is my code as it stands now. Thanks #niton!
Option Explicit
Sub oneEmail_SortedEmailAddresses()
Dim OutApp As Object
Dim OutMail As Object
Dim strVoucher As String
Dim lr As Long
Set OutApp = CreateObject("Outlook.Application")
lr = ActiveSheet.UsedRange.Rows.Count
Dim toAddress As String
Dim i As Long
Dim refundDescYes As Boolean
Dim sigString As String
Dim strbody As String
Dim strname As String
Dim strname2 As String
Dim strCheckNbr As String
Dim strCheckDate As String
Dim strCheckAmt As String
Dim strCheckTst As String
Rows("1:6").Select
Selection.Delete
Range("A1:N1").Select
Selection.AutoFilter
ActiveWorkbook.Worksheets("Check Reconciliation Status").AutoFilter.Sort. _
SortFields.Clear
ActiveWorkbook.Worksheets("Check Reconciliation Status").AutoFilter.Sort. _
SortFields.Add2 key:=Range("A1"), SortOn:=xlSortOnValues, Order:= _
xlAscending, DataOption:=xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("Check Reconciliation Status").AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Rows("2:5").Select
Selection.Delete Shift:=xlUp
Range("i2") = "Yes"
Range("I2").AutoFill Destination:=Range("I2:I" & lr)
For i = 2 To lr
Set OutApp = CreateObject("Outlook.Application")
'sigString = Environ("appdata") &
'"\Microsoft\Signatures\Uncashed Checks.htm"
' If Dir(sigString) <> "" Then
' signature = GetBoiler(sigString)
' Else
' signature = ""
' End If
' Select Case Time
' Case 0.25 To 0.5
' GreetTime = "Good morning"
' Case 0.5 To 0.71
' GreetTime = "Good afternoon"
' Case Else
' GreetTime = "Good evening"
' End Select
' Email address
If ActiveSheet.Range("N" & i).Value <> "" Then
' One email per email address
' This assumes the addresses are sorted
If ActiveSheet.Range("N" & i).Value <> toAddress Then
If Not OutMail Is Nothing Then
If refundDescYes = True Then
OutMail.display
Else
OutMail.Close 1 ' olDiscard
End If
End If
toAddress = ActiveSheet.Range("N" & i).Value
Debug.Print toAddress
Set OutMail = Nothing
refundDescYes = False
Set OutMail = OutApp.CreateItem(0)
With OutMail
strname = Cells(i, "A").Value
strname2 = strname
If InStr(Cells(i, "A"), ",") Then strname2 = Trim(Split(strname, ",")(1))
.To = toAddress
.Subject = "Open Vouchers"
strbody = "<Font face = TimesNewRoman p style=font-size:18.5px color = #0033CC)<br><br>You are receiving this email because our records show you have vouchers open as follows: " & _
"<br><br>Voucher #: " & strVoucher & _
"<br>Check Date: " & strCheckDate & _
"<br>Check Amount: " & strCheckAmt
.HTMLBody = "<Font face = TimesNewRoman p style=font-size:26.5px color = #0033CC><B><p style=font-size:18.5px>Dear " & strname2 & ", " & strbody & "<br>"
.HTMLBody = "<B><br><br>Please reply to this email with any questions." & _
"<br><br>***If we do not receive a reply from you within the next 30 days, you will not be paid."
End With
End If
' Refund Desc
If ActiveSheet.Range("I" & i).Value = "Yes" Then
refundDescYes = True
' Voucher
strCheckTst = "Check Number "
strCheckNbr = Cells(i, "K").Value
strVoucher = strCheckTst & Cells(i, "D").Value & " " & Cells(i, "K").Value
strCheckDate = Cells(i, "L").Value
strCheckAmt = Cells(i, "H").Value
With OutMail
.HTMLBody = .HTMLBody & "<br>" & strVoucher
End With
End If
End If
Next
If Not OutMail Is Nothing Then
If refundDescYes = True Then
OutMail.display
Else
OutMail.Close 1 ' olDiscard
End If
End If
Set OutMail = Nothing
Debug.Print "Done."
End Sub

This example below probably will not work because you didn't post a copy of your data on the worksheet, so I had to make some assumptions. Use this as an example of how to organize your code.
Your main issue is the organization of your code, both inside and outside your loop. In my example, I've simplified the main logic by pulling big blocks of code out into other routines. This should make the overall "flow" of your code easier to read and work with.
Notice a couple things:
Always fully qualify your references to ranges, worksheets, and workbooks.
Avoid magic numbers
Rework the code below into your own data and see if it helps.
EDIT: to send only one email per vendor
Option Explicit
Const NAME_COL As Long = 1
Const VOUCHER_COL As Long = 4
Const DATE_COL As Long = 12
Const CHKNUM_COL As Long = 11
Const AMT_COL As Long = 8
Const TOADDR_COL As Long = 14
Sub Example()
Dim statusWS As Worksheet
Set statusWS = ThisWorkbook.Sheets("Check Reconciliation Status")
' PrepareData statusWS
'--- only do this once
Dim outlookApp As Outlook.Application
Set outlookApp = AttachToOutlookApplication
Dim addresses As Dictionary
Set addresses = GetEmailAddresses(statusWS)
Dim emailAddr As Variant
For Each emailAddr In addresses
'--- create the email now that everything is ready
Dim email As Outlook.MailItem
Set email = outlookApp.CreateItem(olMailItem)
With email
.To = emailAddr
.Subject = "Open Vouchers"
.HTMLBody = BuildEmailBody(statusWS, addresses(emailAddr))
'--- send it now
' (if you want to send it later, you have to
' keep track of all the emails you create)
'.Send
End With
Next emailAddr
End Sub
Sub PrepareData(ByRef ws As Worksheet)
With ws
.Rows("1:6").Delete
.Range("A1:N1").AutoFilter
.AutoFilter.Sort.SortFields.Clear
.AutoFilter.Sort.SortFields.Add2 Key:=Range("A1"), SortOn:=xlSortOnValues, Order:= _
xlAscending, DataOption:=xlSortTextAsNumbers
With .AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
'.Rows("2:5").Delete Shift:=xlUp
.Range("i2") = "Yes"
'--- it only makes sense to find the last row after all the
' other prep and deletions are complete
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
.Range("I2").AutoFill Destination:=Range("I2:I" & lastRow)
End With
End Sub
Function GetEmailAddresses(ByRef ws As Worksheet) As Dictionary
Dim addrs As Dictionary
Set addrs = New Dictionary
With ws
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
'--- each entry in the dictionary is keyed by the email address
' and the item value is a CSV list of row numbers
Dim i As Long
For i = 2 To lastRow
Dim toAddr As String
toAddr = .Cells(i, TOADDR_COL).Value
If addrs.Exists(toAddr) Then
Dim theRows As String
theRows = addrs(toAddr)
addrs(toAddr) = addrs(toAddr) & "," & CStr(i)
Else
addrs.Add toAddr, CStr(i)
End If
Next i
End With
Set GetEmailAddresses = addrs
End Function
Function BuildEmailBody(ByRef ws As Worksheet, _
ByRef rowNumbers As String) As String
Const body1 As String = "<Font face = TimesNewRoman p style=font-size:18.5px color = " & _
"#0033CC)"
Const body2 As String = "<Font face = TimesNewRoman p style=font-size:18.5px color = " & _
"#0033CC)<br><br>You are receiving this email because our " & _
"records show you have vouchers open as follows: "
Const body3 As String = "<B><br><br>Please reply to this email with any questions." & _
"<br><br>***If we do not receive a reply from you within " & _
"the next 30 days, you will not be paid.<br><br>"
With ws
Dim rowNum As Variant
rowNum = Split(rowNumbers, ",")
Dim body As String
body = body1 & TimeOfDayGreeting & .Cells(rowNum(LBound(rowNum)), NAME_COL) & "," & body2
Dim i As Long
For i = LBound(rowNum) To UBound(rowNum)
body = body & "<br><br>Voucher #: " & .Cells(rowNum(i), VOUCHER_COL)
body = body & "<br>Check Date: " & Format(.Cells(rowNum(i), DATE_COL), "dd-mmm-yyyy")
body = body & "<br>Check Amount: " & Format(.Cells(rowNum(i), AMT_COL), "$#,##0.00")
Next i
End With
body = body & body3 & EmailSignature
BuildEmailBody = body
End Function
Function EmailSignature() As String
' Dim sigCheck As String
' sigCheck = Environ("appdata") & "\Microsoft\Signatures\Uncashed Checks.htm"
'
' If Dir(sigCheck) <> vbNullString Then
' EmailSignature = GetBoiler(sigString)
' Else
EmailSignature = vbNullString
' End If
End Function
Function TimeOfDayGreeting() As String
Select Case Time
Case 0.25 To 0.5
TimeOfDayGreeting = "Good morning "
Case 0.5 To 0.71
TimeOfDayGreeting = "Good afternoon "
Case Else
TimeOfDayGreeting = "Good evening "
End Select
End Function
Public Function OutlookIsRunning() As Boolean
'--- quick check to see if an instance of Outlook is running
Dim msApp As Object
On Error Resume Next
Set msApp = GetObject(, "Outlook.Application")
If Err > 0 Then
'--- not running
OutlookIsRunning = False
Else
'--- running
OutlookIsRunning = True
End If
End Function
Public Function AttachToOutlookApplication() As Outlook.Application
'--- finds an existing and running instance of Outlook, or starts
' the application if one is not already running
Dim msApp As Outlook.Application
On Error Resume Next
Set msApp = GetObject(, "Outlook.Application")
If Err > 0 Then
'--- we have to start one
' an exception will be raised if the application is not installed
Set msApp = CreateObject("Outlook.Application")
End If
Set AttachToOutlookApplication = msApp
End Function

Related

Pull Data By Vendor from Excel for Outlook Email

I have a list of vendors that I sort by name and then have a macro go through and pull out data pieces from fields and place them inside an Outlook email. Pretty straightforward until I get to vendors with multiple lines, as I then need to have the code know to look at all the lines for that vendor and pull their info and place it into a list in the email (so they do not get multiple emails all at once).
The above image is a sample of the list after I have sorted it by vendor. So I would want one email for each vendor, but vendor1 would need the data from Invoice, Paid Amt, Check ID, and Check Dt for both of his lines. Vendor 2 would just have one line, and Vendor3 would have 3. I need a way to have the macro know to look at the vendor name (or Vendor #) and know that it needs to pull the data from the next line and include it in that same email until it is at the next vendor.
I am not a programmer and am trying to make this work. Below is an example of what I have been trying so far but it only creates one email for every line. Hoping someone smarter than me can help me. Thanks.
Dim OutApp As Object
Dim OutMail As Object
Dim cell As Range
Dim strDir As String
Dim strFilename As String
Dim sigString As String
Dim strbody As String
Dim strname As String
Dim strName1 As String
Dim strDept As String
Dim strName2 As String
Dim lr As Long
Dim oItem As Object
Dim dteSat As Date
Dim nextSat As Date
Dim lastRow As Long
Dim ws As String
'Link to Outlook, use GetBoiler function to pull email signature
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
sigString = Environ("appdata") & _
"\Microsoft\Signatures\Uncashed Checks.htm"
If Dir(sigString) <> "" Then
signature = GetBoiler(sigString)
Else
signature = ""
End If
Select Case Time
Case 0.25 To 0.5
GreetTime = "Good morning"
Case 0.5 To 0.71
GreetTime = "Good afternoon"
Case Else
GreetTime = "Good evening"
End Select
'Define the date for the next Saturday
With Item
K = Weekday(TODAY)
dteChk = Weekday(TODAY) - 30
dteSat = Now() + (10 - K)
nextSat = Date + 7 - Weekday(Date, vfSaturday)
End With
'Select the currently active sheet and insert a column next to column I, then fill it with the word 'yes'. The yes values will act as triggers to tell the code to run for that row.
'Delete first 7 rows of report. Find the Paid Amt header and then replace that column with a re-formatted one that shows the full numbers with decimals and zeroes. Change column E
'to UPPER case using the index and upper functions.
lr = ActiveSheet.UsedRange.Rows.Count
lastRow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
Rows("1:7").Select
Columns("C").SpecialCells(xlBlanks).EntireRow.Delete
Columns("I:I").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Set rng8 = Range("A1:Z1").Find("Paid Amt")
Set rng9 = ActiveSheet.Range(rng8, ActiveSheet.Cells(Rows.Count, rng8.Column).End(xlUp).Address)
rng9.Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
ActiveCell.FormulaR1C1 = "=TEXT(RC[+1],""#.00"")"
ActiveCell.Copy
Range(ActiveCell.Offset(350 - ActiveCell.Row, 0), ActiveCell.Offset(1, 0)).Select
ActiveSheet.Paste
ActiveCell.Offset.Resize(1).EntireColumn.Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
ActiveCell.Offset(0, 1).Select
ActiveCell.Offset.Resize(1).EntireColumn.Select
Application.CutCopyMode = False
Selection.Delete Shift:=xlToRight
Range("i2") = "Yes"
Range("I2").AutoFill Destination:=Range("I2:I" & lr)
[e2:e350] = [INDEX(UPPER(e2:e350),)]
'Begin a loop that looks at all the yes values in column I and then begins to create emails. Define the columns to be used for data by looking for the header names such as Paid Amt.
For Each cell In Columns("G").Cells.SpecialCells(xlCellTypeConstants)
If cell.Value Like "?*#?*.?*" And _
LCase(Cells(cell.Row, "I").Value) = "yes" Then
Set OutMail = OutApp.CreateItem(0)
Set rng8 = Range("A1:Z1").Find("Paid Amt")
Set foundCell = Cells(cell.Row, rng8.Column)
Set rng9 = Range("A1:AG1").Find("Check Dt")
Set foundCell1 = Cells(cell.Row, rng9.Column)
Set rng12 = Range("A1:AG1").Find("Student Perm Address")
Set foundcell2 = Cells(cell.Row, rng12.Column)
'Create the actual email data, definiing the body and recipients/names, etc, based on the values in the cells noted below. Sentonbehalf is the 'From' field. Change font color
'using the hexadecimal codes. The one used here 1F497D is Blue-Gray.
With OutMail
strname = Cells(cell.Row, "A").Value
strName2 = Trim(Split(strname, ",")(1))
strName3 = Cells(cell.Row, "R").Value
strName4 = Cells(cell.Row, "E").Value
strbody = "<Font face = TimesNewRoman p style=font-size:18.5px color = #0033CC)<br><br>You are receiving this email because you have an uncashed check that was sent to you over 30 days ago. " & _
"Please cash or deposit your check.<br><br>" & _
"<B>The amount of the check is $" & foundCell & " and is dated " & foundCell1 & ". The check was mailed to the following address: <br><br>" & _
"<ul>" & foundcell2 & "<br></B></ul>"
.SentOnBehalfOfName = "accounts-payable#salemstate.edu"
.To = cell.Value
.Subject = "Uncashed Check from Salem State University"
.HTMLBody = "<Font face = TimesNewRoman p style=font-size:26.5px color = #0033CC><B>" & "Important Information Regarding Your Student Account </B><br><br><p style=font-size:18.5px> Dear " & strName2 & ", " & strbody & "<br>" & signature & "<HTML><BODY><IMG src='C:\Users\gmorris\Pictures\Saved Pictures\220px-Salem_State_University_logo.png' /></BODY></HTML>"
.display 'Or use Send
End With
Set OutMail = Nothing
End If
Next cell
End Sub
If the email addresses are sorted:
When the email address matches the previous:
Bypass creating email, append to the body.
When there is a new email address:
Send the existing mail before creating new email.
Option Explicit
Sub oneEmail_SortedEmailAddresses()
Dim OutApp As Object
Dim OutMail As Object
Dim strVoucher As String
Dim lr As Long
Set OutApp = CreateObject("Outlook.Application")
lr = ActiveSheet.UsedRange.Rows.Count
Dim toAddress As String
Dim i As Long
Dim refundDescYes As Boolean
For i = 2 To lr
' Email address
If ActiveSheet.Range("N" & i).Value <> "" Then
' One email per email address
' This assumes the addresses are sorted
If ActiveSheet.Range("N" & i).Value <> toAddress Then
If Not OutMail Is Nothing Then
If refundDescYes = True Then
OutMail.display
Else
OutMail.Close 1 ' olDiscard
End If
End If
toAddress = ActiveSheet.Range("N" & i).Value
Debug.Print toAddress
Set OutMail = Nothing
refundDescYes = False
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = toAddress
.Subject = "Uncashed Check from Salem State University"
End With
End If
' Refund Desc
If ActiveSheet.Range("I" & i).Value = "Yes" Then
refundDescYes = True
' Voucher
strVoucher = Cells(i, "D").Value
With OutMail
.HTMLBody = .HTMLBody & "<br>" & strVoucher & "<br>"
End With
End If
End If
Next
If Not OutMail Is Nothing Then
If refundDescYes = True Then
OutMail.display
Else
OutMail.Close 1 ' olDiscard
End If
End If
Set OutMail = Nothing
Debug.Print "Done."
End Sub

How to loop through list, find data and send in HTML email?

I have the following list with one, or multiple entries for a specific ID.
I have a second list with with unique IDs and email addresses.
I need to loop through the list, send an email to every ID and list data from each matching row in the email, also mentioning the total amount.
Example of the email sent to ID 1234 foo#bar.com:
What I have so far:
Sub SendEmail()
Dim strbody1 As String
Dim strbody2 As String
Dim Signature As String
Dim i As Integer, Mail_Object, Email_Subject, o As Variant, lr As Long
lr = Cells(Rows.Count, "A").End(xlUp).Row
strbody1 = "Hi,<br><br>" & _
"Test.<br><br>"
strbody2 = "Test1.<br><br>" & _
"Foobar,"
Signature = "<H4><B>My Name</B></H4>" & _
"Something<br>" & _
"Something<br>" & _
"T: +1 000 000 000<br>" & _
"foo#bar.com<br>" & _
"www.bar.com"
If MsgBox(("This will send all emails in the list. Do you want to proceed?"), vbYesNo) = vbNo Then Exit Sub
Set Mail_Object = CreateObject("Outlook.Application")
For i = 2 To lr
With Mail_Object.CreateItem(o)
.Subject = Range("B" & i).Value
.SentOnBehalfOfName = "foo#bar.com"
.To = Range("A" & i).Value
.Body = Range("C" & i).Value
.HTMLBody = strbody1 & strbody2 & Signature
.Send 'disable display and enable send to send automatically
End With
Next i
MsgBox "E-mail successfully sent", 64
Application.DisplayAlerts = False
Set Mail_Object = Nothing
End Sub
You can put the IDs into a Dictionary Object. Then scan the data for each ID in turn adding the rows with that ID to an html table. If performance is an issue copy the data to an array first and scan that.
Option Explicit
Sub SendEMail()
Const WS_ID = "Sheet1"
Const WS_DATA = "Sheet2"
Const HEAD = "<head><style>body {font: 20px Verdana;} " & _
" .amount {text-align:right;}</style></head>"
Const TABLE = "<table cellspacing=""0"" cellpadding=""5""" & _
" border=""1"">" & _
"<tr bgcolor=""#EEEEEE""><th>REF</th><th>Amount</th></tr>"
Const TXT = "This is a test email"
Dim wb As Workbook, ws As Worksheet
Dim iLastRow As Long, i As Long
Dim dictID As Object, ID, addr As String
Set dictID = CreateObject("Scripting.Dictionary")
' get list of IDS
Set wb = ThisWorkbook
Set ws = wb.Sheets(WS_ID)
iLastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
For i = 2 To iLastRow
ID = Trim(ws.Cells(i, "A"))
addr = Trim(ws.Cells(i, "B"))
If dictID.exists(ID) Then
MsgBox ID & " is duplicated", vbCritical, "Duplicate ID"
Exit Sub
ElseIf InStr(1, addr, "#") > 0 Then
dictID.Add ID, addr
End If
Next
Dim objOut
Set objOut = CreateObject("Outlook.Application")
' scan data
Dim total As Double, htm As String
Set ws = wb.Sheets(WS_DATA)
iLastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
For Each ID In dictID
total = 0
addr = dictID(ID)
' build html table
htm = "<html>" & HEAD & "<body><p>" & TXT & "</p>" & TABLE
For i = 2 To iLastRow
If ws.Cells(i, "A") = CStr(ID) Then
htm = htm & "<tr><td>" & ws.Cells(i, "B") & _
"</td><td class=""amount"">" & ws.Cells(i, "C") & "</td></tr>" & vbCrLf
total = total + ws.Cells(i, "C")
End If
Next
total = Format(total, "#,##0")
htm = htm & "<tr bgcolor=""#CCFFCC"" style=""font-weight:bold""><td>TOTAL</td>" & _
"<td class=""amount"">" & total & "</td></tr></table><br/>" & _
"<p>The total amount is " & total & "</p></body></html>"
' send email
Call SendOneEMail(objOut, CStr(ID), addr, htm)
Next
MsgBox dictID.Count & " emails sent", vbInformation
End Sub
Sub SendOneEMail(objOut, sID As String, sTo As String, htm As String)
' create email
With objOut.CreateItem(0) 'olMailItem
.Subject = sID
.SentOnBehalfOfName = "foo#bar.com"
.To = sTo
.HTMLBody = htm
.Display
'.Send 'disable display and enable send to send automatically
End With
End Sub

How to keep the hyperlinks, in table column, clickable when sending to body of mail?

I am trying to send an email via Outlook using VBA.
I have a column filled with hyperlinks. When the email is constructed, the hyperlinks turns into plain text and are not clickable.
I reference the column using Cells(row_num,1) because all the hyperlinks are unique.
How to make them show up as hyperlinks?
Sub SendEmail()
Dim olook As Outlook.Application
Dim omailitem As Outlook.MailItem
Dim i As Byte, row_num As Byte
row_num = 2
Set olook = New Outlook.Application
For i = 1 To 15
Set omailitem = olook.CreateItem(0)
With omailitem
.To = Sheets(1).Cells(row_num, 2)
.Subject = "Tool Notification"
.Body = "Hello!" & vbNewLine & vbNewLine & _
"Below are the link(s) to the task(s) that you have due on: " & _
Cells(row_num, 4).Value & _
vbNewLine & vbNewLine & "Link: " & Cells(row_num, 1).Value & _
vbNewLine & vbNewLine & "Thank you," & _
vbNewLine & vbNewLine & "Tool"
.Display
End With
row_num = row_num + 1
Next
End Sub
Sample Data
https://i.stack.imgur.com/m9Stx.png
Check the code's comments and adjust it to fit your needs.
This should be pasted in a standard module.
EDIT: Adjusted to accumulate links by sender
Code:
Option Explicit
Sub SendEmail()
Dim olApp As Outlook.Application
Dim olMail As Outlook.MailItem
Dim targetSheet As Worksheet
Dim targetRange As Range
Dim cell As Range
Dim lastRow As Long
Dim recipientAddr As String
Dim bodyContent As String
Dim duedateFormat As String
Dim linkFormat As String
' Set reference to target Sheet (replace 1 with the sheet's name or codename)
Set targetSheet = ThisWorkbook.Worksheets(1)
' Find last cell in column b
lastRow = targetSheet.Cells(targetSheet.Rows.Count, 2).End(xlUp).Row
' Set target range
Set targetRange = targetSheet.Range("B2:B" & lastRow)
' Start new outlook instance
Set olApp = New Outlook.Application
' Loop through each cell in column B
For Each cell In targetRange.Cells
' If cell has data
If cell.Value <> vbNullString Then
' Check if is the same recipient as next
If cell.Value = cell.Offset(1, 0).Value Then
linkFormat = linkFormat & "" & cell.Offset(0, -1) & "<br>"
Else
linkFormat = linkFormat & "" & cell.Offset(0, -1) & ""
' Collect email data from cells
recipientAddr = cell.Value
duedateFormat = Format(cell.Offset(0, 2).Value, "mm-dd-yyyy")
' Build the link string
bodyContent = "Hello!<br><br>" & _
"Below are the link(s) to the task(s) that you have due on: " & duedateFormat & "<br><br>" & _
"Link(s): <br>" & _
linkFormat & "<br><br>" & _
"Thank you,<br><br>" & _
"Tool"
' Create the mail item and display it
Set olMail = olApp.CreateItem(olMailItem)
With olMail
.To = cell.Value
.Subject = "Tool Notification"
.HTMLBody = bodyContent
.Display
End With
' Reset the link
linkFormat = vbNullString
End If
End If
Next cell
End Sub
Let me know if it works

Need to send email to multiple reciepent from filter data

I wanted to send email to multiple recipient as per their fund code. for eg. in given image I want email for QR fund in column A to be sent out to B2,B3 and B4 in same email and subject line should be "C2" for next I want email for RTIO fund in column A to be sent out to B5, B7 and B8 in same email and subject line should be "C5" and so on
Sub SendMultipleEmails()
Dim Mail_Object, OutApp As Variant
With ActiveSheet
lastrow = .Cells(.Rows.Count, "b").End(xlUp).Row
End With
For i = 2 To lastrow
Set Mail_Object = CreateObject("Outlook.Application")
Set OutApp = Mail_Object.CreateItem(0)
With OutApp
.Subject = "Your subject here"
.Body = "Your message here"
.To = Cells(i, 2).Value
.dISPLAY
End With
I am not able to apply filter condition and get multiple email recipient in one email
Try this code:
Sub SendMultipleEmailsaa()
Dim Mail_Object, OutApp As Object
Dim ws As Worksheet: Set ws = ActiveSheet
Dim arr() As Variant
LastRow = ws.Cells(ws.Rows.Count, "b").End(xlUp).row
arr = ws.Range("A2:A" & LastRow)
Set Mail_Object = CreateObject("Outlook.Application")
first = 2
For i = LBound(arr) To UBound(arr)
If i = UBound(arr) Then GoTo YO
If arr(i + 1, 1) = arr(i, 1) Then
first = WorksheetFunction.Min(first, i + 1)
Else
YO:
Set OutApp = Mail_Object.CreateItem(0)
With OutApp
.Subject = ws.Range("C" & i + 1).Value
.Body = "Your message here"
.Display
.To = ws.Range("A" & i + 1).Value
For j = first To i
.Recipients.Add ws.Range("A" & j).Value
Next
first = i + 2
End With
End If
Next
End Sub
To automatically sort add this code below the calculation on LastRow in above code:
ws.Sort.SortFields.Clear
ws.Sort.SortFields.Add2 Key:=ws.Range("A2:A" & LastRow), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ws.Sort
.SetRange ws.UsedRange
.Header = False
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Another Update:
Dim bc As String
With OutApp
.Subject = ws.Range("C" & i + 1).Value
.Body = "Your message here"
.Display
bc = ws.Range("A" & i + 1).Value
For j = first To i
bc = bc & ";" & ws.Range("A" & j).Value
Next
.BCC = bc
first = i + 2
End With
Here is my solution:
Option Explicit
Public Sub Main()
Dim rngSource As Range: Set rngSource = ExpandRange(ThisWorkbook.Worksheets("Sheet1").Range("A2"))
ReadDataAndSendAllMail rngSource
End Sub
Public Function ExpandRange(rngTopLeftCell As Range) As Range
With rngTopLeftCell.Worksheet
Set ExpandRange = rngTopLeftCell.Resize( _
.Cells(.Rows.Count, rngTopLeftCell.Column).End(xlUp).Row - rngTopLeftCell.Row + 1, _
.Cells(rngTopLeftCell.Row, .Columns.Count).End(xlToLeft).Column - rngTopLeftCell.Column + 1)
End With
End Function
Public Sub ReadDataAndSendAllMail(rngSource As Range)
Dim dctData As Dictionary: Set dctData = ReadData(rngSource)
SendAllMail dctData
End Sub
Public Function ReadData(rngSource As Range) As Dictionary
Dim dctResult As Dictionary: Set dctResult = New Dictionary
Dim rngRecord As Range: For Each rngRecord In rngSource.Rows
Dim dctRecord As Dictionary: Set dctRecord = New Dictionary
dctRecord.Add "Fund", rngRecord.Cells(1, 1).Value
dctRecord.Add "Email", rngRecord.Cells(1, 2).Value
dctRecord.Add "Subject", rngRecord.Cells(1, 3).Value
dctRecord.Add "Attachment", rngRecord.Cells(1, 4).Value
If Not dctResult.Exists(dctRecord("Fund")) Then
dctResult.Add dctRecord("Fund"), New Collection
End If
dctResult(dctRecord("Fund")).Add dctRecord
Next rngRecord
Set ReadData = dctResult
End Function
Public Sub SendAllMail(dctData As Dictionary)
Const cstrEmailDelimiter As String = "; " ' Note: Observe which delimiter your local version of Outlook uses and replace this value with it
Dim moaOutlook As Outlook.Application: Set moaOutlook = New Outlook.Application
Dim varFund As Variant: For Each varFund In dctData.Keys
Dim strFund As String: strFund = vbNullString
Dim strTo As String: strTo = vbNullString
Dim strSubject As String: strSubject = vbNullString
Dim strBody As String: strBody = vbNullString
Dim strAttachmentPath As String: strAttachmentPath = vbNullString
Dim dctRecord As Dictionary: For Each dctRecord In dctData(varFund)
strFund = dctRecord("Fund")
strTo = strTo & cstrEmailDelimiter & dctRecord("Email")
strSubject = dctRecord("Subject")
strBody = vbNullString ' Note: Replace vbNullString with some text for the message body
strAttachmentPath = dctRecord("Attachment")
Next dctRecord
strTo = Mid(strTo, Len(cstrEmailDelimiter) + 1)
SendMail moaOutlook, strTo, strSubject, vbNullString, strAttachmentPath
Next varFund
moaOutlook.Quit
End Sub
Public Sub SendMail(moaOutlook As Outlook.Application, strTo As String, strSubject As String, strBody As String, strAttachmentPath As String)
Dim omiMailItem As Outlook.MailItem: Set omiMailItem = moaOutlook.CreateItem(olMailItem)
With omiMailItem
.To = strTo
.Subject = strSubject
.Body = strBody ' Note use .HTMLBody if you want to send an HTML email
.Attachments.Add strAttachmentPath
.display ' Note: If you want to manually press the send button, otherwise comment out this line
' .send ' Note: If you want to automatically send it, uncomment this line
End With
End Sub
I hope the function names make it easier to understand and reuse. I tested it, and worked for me.

Sending email with body of message being the contents of a cell, including new-line formatting?

I'm trying to send an email with the body of the message consisting of the contents of a text box. So far I've tried pulling in the text box through vba as a string, but that takes away all the new-lines formatting. Is there a way to get the text box contents exactly as they are into the email?
Sub Send_Email()
Dim OutApp As Object
Dim OutMail As Object
Dim title As String, emailto As String
Dim texts As String
title = Range("email_subject").Value
emailto = Range("email_to").Value
texts = Worksheets("Input").Shapes("TextBox 2").TextFrame.Characters.Text
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 = emailto
.Subject = title
.HTMLBody = texts
.display
End With
On Error GoTo 0
End Sub
Please find an example below that might help with your question. You will have global variable that will hold information from excel worksheet and use them in the email. Whithout a image on how your data looks cannot really guess what you are trying to do. Maybe you can separete the text in different cells that way you can loop throught and put them in different variables and you can construct your email in the SendEmail procedure. Or if you have the same text and it doesn't change you can make it as per the below example.
Option Explicit
Dim titleName As String
Dim firstName As String
Dim lastName As String
Dim fullName As String
Dim clientEmail As String
Dim ccEmail As String
Dim bccEmail As String
Dim emailMessage As String
Sub GenerateInfo()
Dim WS As Worksheet
Dim lrow As Long
Dim cRow As Long
Set WS = ActiveSheet
With WS
lrow = .Range("E" & .Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
For cRow = 2 To lrow
If Not .Range("L" & cRow).value = "" Then
titleName = .Range("D" & cRow).value
firstName = .Range("E" & cRow).value
lastName = .Range("F" & cRow).value
fullName = firstName & " " & lastName
clientEmail = .Range("L" & cRow).value
Call SendEmail
.Range("Y" & cRow).value = "Yes"
.Range("Y" & cRow).Font.Color = vbGreen
Else
.Range("Y" & cRow).value = "No"
.Range("Y" & cRow).Font.Color = vbRed
End If
Next cRow
End With
Application.ScreenUpdating = True
MsgBox "Process completed!", vbInformation
End Sub
Sub SendEmail()
Dim outlookApp As Object
Dim outlookMail As Object
Dim sigString As String
Dim Signature As String
Dim insertPhoto As String
Dim photoSize As String
Set outlookApp = CreateObject("Outlook.Application")
Set outlookMail = outlookApp.CreateItem(0)
'Change only Mysig.htm to the name of your signature
sigString = Environ("appdata") & _
"\Microsoft\Signatures\Marius.htm"
If Dir(sigString) <> "" Then
Signature = GetBoiler(sigString)
Else
Signature = ""
End If
insertPhoto = "C:\Users\marius\Desktop\Presale.jpg" 'Picture path
photoSize = "<img src=""cid:Presale.jpg""height=400 width=400>" 'Change image name here
emailMessage = "<BODY style=font-size:11pt;font-family:Calibri>Dear " & titleName & " " & fullName & "," & _
"<p>I hope my email will find you very well." & _
"<p>Our <strong>sales preview</strong> starts on Thursday the 22nd until Sunday the 25th of November." & _
"<p>I look forward to welcoming you into the store to shop on preview.<p>" & _
"<p> It really is the perfect opportunity to get some fabulous pieces for the fast approaching festive season." & _
"<p>Please feel free to contact me and book an appointment." & _
"<p>I look forward to seeing you then." & _
"<p>" & photoSize & _
"<p>Kind Regards," & _
"<br>" & _
"<br><strong>Marius</strong>" & _
"<br>Assistant Store Manager" & _
"<p>"
With outlookMail
.To = clientEmail
.CC = ""
.BCC = ""
.Subject = "PRIVATE SALE"
.BodyFormat = 2
.Attachments.Add insertPhoto, 1, 0
.HTMLBody = emailMessage & Signature 'Including photo insert and signature
'.HTMLBody = emailMessage & Signature 'Only signature
.Importance = 2
.ReadReceiptRequested = True
.Display
.Send
End With
Set outlookApp = Nothing
Set outlookMail = Nothing
End Sub
Function GetBoiler(ByVal sFile As String) As String
Dim fso As Object
Dim ts As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(sFile).OpenAsTextStream(1, -2)
GetBoiler = ts.readall
ts.Close
End Function

Resources