Email people based on a range VBA - excel

I have the following data set
I have the following code to send an email to each row. How do I make it group the rows and send them all as 1 email like in the picture
Here is an example of the email I am looking to build
At the moment the code steps through each row and builds and email off of that. I want it to check the A Column for a code and find all other columns with the same code and build one email using information from all of their columns
Sub SendIntransitEmail()
Dim Mail_Object, OutApp As Variant
Dim eRng1, eRng2, eRng3, rng1, rng2, rng3, rng4, cl As Range
Dim sTo, sCC, sLoc, sFile1, sFile2, sHeader, sBody As String
Set rng4 = ThisWorkbook.Worksheets("sheet1").Range("B4")
Dim intNum As Integer
intNum = ThisWorkbook.Worksheets("sheet1").Range("B1")
For i = 5 To intNum
On Error Resume Next
Set Mail_Object = CreateObject("Outlook.Application")
Set OutApp = Mail_Object.CreateItem(0)
Set rng1 = ThisWorkbook.Worksheets("sheet1").Range("A" & i)
Set eRng1 = ThisWorkbook.Worksheets("sheet1").Range(Cells(i, 5), Cells(i, 8))
Set eRng2 = ThisWorkbook.Worksheets("sheet1").Range(Cells(i, 9), Cells(i, 40))
Set eRng3 = ThisWorkbook.Worksheets("sheet1").Range(Cells(4, 2), Cells(4, 4))
Set eRng4 = ThisWorkbook.Worksheets("sheet1").Range(Cells(i, 2), Cells(d, 2))
For Each cl In eRng1
sTo = sTo & ";" & cl.Value
Next
sTo = Mid(sTo, 2)
For Each cl In eRng2
sCC = sCC & ";" & cl.Value
Next
For Each cl In eRng3
sDelivery = sDelivery & cl.Value
Next
For Each cl In eRng4
sTrailer = sTrailer & cl.Value
Next
For Each cl In eRng5
sShipper = sShipper & cl.Value
Next
sCC = Mid(sCC, 2)
Set OApp = CreateObject("Outlook.Application")
Set OMail = OApp.CreateItem(0)
With OutApp
.To = sTo
.CC = sCC
.Subject = "Location " & rng1
.BodyFormat = olFormatHTML
.HTMLBody = "<p> Hello, </p><p>Your delivery information is below: </p><p>
Deliver Number: " & sDelivery & "<p/> <p> Trailer Number: " & sTrailer & "
<p/><p>Shipper ID: " & sShipper & "<p/><p>Best Regards </p>"
.display
End With
Set OMail = Nothing
Set OApp = Nothing
Set eRng1 = Nothing
Set eRng2 = Nothing
sTo = ""
sCC = ""
Next i
End Sub

I see what you're trying to do now. You should shift your loop from running on each column, to rather doing by row. Something like... if row doesn't have member above, collect all appropriate members in the row's column, then run a loop through remaining rows, testing to see if they MATCH and then appending them to the email.
At the moment, I'm too lazy to write this out but here's a custom formula that might help you that will only test if the member in the respective row exists above.
Sub SendIntransitEmail()
Dim Mail_Object, OutApp As Variant
Dim eRng1, eRng2, eRng3, rng1, rng2, rng3, rng4, cl As Range
Dim sTo, sCC, sLoc, sFile1, sFile2, sHeader, sBody As String
Set rng4 = ThisWorkbook.Worksheets("sheet1").Range("B4")
Dim intNum As Integer
intNum = ThisWorkbook.Worksheets("sheet1").Range("B1")
Set Mail_Object = CreateObject("Outlook.Application")
For i = 5 To intNum
On Error Resume Next 'I wouldn't use this...
'test if first instance of plant
If New_Plant_Test(ThisWorkbook.Worksheets("sheet1").Cells(i, 1)) = True Then
'run a loop from this row all the way down to populate the respective emails,
'example:
For Each rcell In Range(ThisWorkbook.Worksheets("sheet1").Cells(i, 1), ThisWorkbook.Worksheets("sheet1").Cells(intNum, 1)).Cells
'apply respective values to variables in that row.
'this should probably be a separate private macro.
Next rcell
'send email and clear variables and clear variables
Else
'skips as plant already existed
End If
Next i 'continue loop by each row
End Sub
Private Function New_Plant_Test(rng As Range) As Boolean
Dim tRow As Long, ws As Worksheet
tRow = rng.Row
Set ws = Sheets(rng.Parent.Name)
On Error GoTo NewMember
tRow = Application.WorksheetFunction.Match(ws.Cells(tRow, 1), Range(ws.Cells(1, 1), ws.Cells(tRow - 1, 1)), False)
On Error GoTo 0
Exit Function
NewMember:
New_Plant_Test = True
End Function

Related

Searching for unique value and call sub, if not go to next cell

I am trying to create automatic call for a sub based on unique values.
Column E
The order is in column E
Sub FindDate()
Dim Cell As Range
'For Each Cell In ActiveSheet.Range("A1:A50")
' If Cell.Value = [Today()] Then
' Cell.Select
'ActiveCell.Offset(0, 4).Select
' End If
'Exit For
'Next
For Each Cell In ActiveSheet.Range("E2:E100")
If ActiveCell.Value = "" Then
End If
Exit For
Next
For Each Cell In ActiveSheet.Range("E2:E100")
If ActiveCell.Value = ActiveCell.Offset(-1, 0) Then
ActiveCell.Offset(1, 0).Select
Call EmailOrder
' ElseIf ActiveCell.Value <> ActiveCell.Offset(-1, 0) Then Call EmailOrder
'ElseIf ActiveCell.Value = "" Then Exit Sub
End If
Next Cell
End Sub
At the moment with this code (I know it is a really messy but I am just a VBA beginner) when I select the second PAU21001316 (from the picture) then it is calling my EmailOrder sub for PAU21001316 and PAU21001318 but not for the PAU21001319 and PAU21001320.
The code should do : If I select a cell, let's say PAU21001309 to look if the cell above ( or below) is the same value, if it's the same to move one cell below if not to run Call EmailOrder and after to move to the next cell and to do the same. Then If a cell is empty to stop.
The point is to run every unique value at the same time.
The other thing that I was trying to do (the first code as comment) was to go to the today's date and move 4 columns which will go to the first Order number. It's moving the active cell but after that do nothing, just looping.
If anyone can help me to finish my code I will be grateful.
Sub EmailOrder(c As Range)
Dim ActiveC As Variant
Dim DirFile As String
Dim OutApp As Outlook.Application
Dim OutMail As Outlook.MailItem
Dim strbody As String
Dim SigString As String
Dim Signature As String
Dim xOutMsg As String
Dim Timenow As String
Dim signImageFolderName As String
Dim completeFolderPath As String
Dim colFiles As New Collection
'GetFiles "C:\xxx\", ActiveC & ".pdf", True, colFiles
'If colFiles.Count > 0 Then
' 'work with found files
'End If
If Time < TimeValue("12:00:00") Then
Timenow = "Good Morning"
ElseIf Time > TimeValue("12:00:00") And Time < TimeValue("17:00:00") Then
Timenow = "Good Afternoon"
Else
Timenow = "Good Evening"
End If
xOutMsg = Timenow & ", <br> <br> xxx<br/>"
ActiveC = Application.ActiveCell.Value
Dim sRes As String
Dim po As Range
Dim rg As Range
Dim b2 As Range
Set po = ActiveCell.Offset(0, 3)
Set rg = Sheets("Email").Range("B1:D200")
Set b2 = po
sRes = Application.VLookup(b2, rg, 3, True)
'Optimize Code
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.DisplayAlerts = False
DirFile = "C:\xxx\" & ActiveC & ".pdf"
If Dir(DirFile) = "" Then
MsgBox "File does not exist", vbCritical
End If
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(olMailItem)
'Change only Mysig.htm to the name of your signature
SigString = Environ("appdata") & _
"\Microsoft\Signatures\xxx.htm"
signImageFolderName = "xxxfiles"
completeFolderPath = Environ("appdata") & "\Microsoft\Signatures\" & signImageFolderName
If Dir(SigString) <> "" Then
Signature = GetBoiler(SigString)
Signature = VBA.Replace(Signature, signImageFolderName, completeFolderPath)
Else
Signature = ""
End If
'Create Outlook email with attachment
On Error Resume Next
With OutMail
.To = sRes
.CC = ""
.BCC = ""
.Subject = "xxx " & ActiveC
.HTMLBody = xOutMsg & "<br>" & Signature
.Attachments.Add "C:xxx\" & ActiveC & ".pdf"
.Display
End With
Call FindDate
On Error GoTo 0
Set OutMail = Nothing
Set OutApp = Nothing
End Sub
Function GetBoiler(ByVal sFile As String) As String
'Dick Kusleika
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
This is the main code, made form different codes. The main purpose is to get the value of the active cell and look in to the folder (I couldn't do to look in to the sub folders) for the file name.pdf and the to attach it to the email. The other part is to look for the supplier name in column H and VLOOKUP to another sheet "Email" for the supplier email and add it to the "To" section. The other code is for the signature and the body of the email.
The code is working but only when I select the specific cell. But it will be faster if is doing every PO for the day automatically.
Try this:
Sub Tester()
Dim f As Range, c As Range
Set f = Range("A1:A50").Find(Date) 'Look for today's date
If f Is Nothing Then Exit Sub 'Today not found....
Set c = f.Offset(0, 4) 'move over to Col E
Do While Len(c.Value) > 0
If c.Offset(1, 0).Value <> c.Value Then
EmailOrder c 'pass cell directly to your called sub
End If
Set c = c.Offset(1, 0) 'move down one row
Loop
End Sub
Sub EmailOrder(c As Range)
Const FLDR As String = "C:\xxx\" 'start search here
Dim ActiveC As Variant
Dim OutApp As Outlook.Application
Dim OutMail As Outlook.MailItem
Dim strbody As String
Dim SigString As String
Dim Signature As String
Dim sRes As String
Dim po
Dim rg As Range, b2 As Range
Dim signImageFolderName As String, completeFolderPath As String
Dim colFiles As Collection
ActiveC = c.Value
po = c.Offset(0, 3).Value
Set rg = Sheets("Email").Range("B1:D200")
sRes = Application.VLookup(po, rg, 3, True) 'False?
Set colFiles = GetMatches(FLDR, ActiveC & ".pdf") 'find any matches
If colFiles.Count = 0 Then
MsgBox "File '" & ActiveC & ".pdf' does not exist", vbCritical
Exit Sub
End If
'what to do if >1 files found?
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(olMailItem)
'Change only Mysig.htm to the name of your signature
SigString = Environ("appdata") & "\Microsoft\Signatures\xxx.htm"
signImageFolderName = "xxxfiles"
completeFolderPath = Environ("appdata") & "\Microsoft\Signatures\" & signImageFolderName
If Dir(SigString) <> "" Then
Signature = VBA.Replace(GetBoiler(SigString), signImageFolderName, completeFolderPath)
End If
With OutMail
.To = sRes
.CC = ""
.BCC = ""
.Subject = "xxx " & ActiveC
.HTMLBody = TimeGreeting & ", <br> <br> xxx<br/>" & Signature
.Attachments.Add colFiles(1).Path 'assuming you only want the first match if >1
.Display
End With
Call FindDate
End Sub
Function TimeGreeting() As String
If Time < TimeValue("12:00:00") Then
TimeGreeting = "Good Morning"
ElseIf Time > TimeValue("12:00:00") And Time < TimeValue("17:00:00") Then
TimeGreeting = "Good Afternoon"
Else
TimeGreeting = "Good Evening"
End If
End Function
Function for file searching:
'Return a collection of file objects given a starting folder and a file pattern
' e.g. "*.txt"
'Pass False for last parameter if don't want to check subfolders
Function GetMatches(startFolder As String, filePattern As String, _
Optional subFolders As Boolean = True) As Collection
Dim fso, fldr, f, subFldr
Dim colFiles As New Collection
Dim colSub As New Collection
Set fso = CreateObject("scripting.filesystemobject")
colSub.Add startFolder '<< start with the top-level folder
Do While colSub.Count > 0
Set fldr = fso.getfolder(colSub(1))
colSub.Remove 1 '<< remove from queue
For Each f In fldr.Files 'check all files
If UCase(f.Name) Like UCase(filePattern) Then colFiles.Add f
Next f
If subFolders Then 'add subfolders to queue for listing
For Each subFldr In fldr.subFolders
colSub.Add subFldr.Path
Next subFldr
End If
Loop
Set GetMatches = colFiles
End Function

Formatting a table using VBA that is being copied to an email

I have been struggling with this issue for awhile now and I would be very grateful for any help offered. So I have the code that is generating an email from an excel file that I have. The issue is that when the email is pasted over the table does not format correctly. I have attached a screenshot of what the output looks like and the code is below.
Sub Send_Email()
'Updated by Extendoffice 20200119
Dim xRg As Range
Dim I, J As Long
Dim xAddress As String
Dim xEmailBody As String
Dim xMailOut As Outlook.MailItem
Dim xOutApp As Outlook.Application
On Error Resume Next
xAddress = ActiveWindow.RangeSelection.Address
Set xRg = Range("A9:E32")
If xRg Is Nothing Then Exit Sub
Application.ScreenUpdating = False
Set xOutApp = CreateObject("Outlook.Application")
Set xMailOut = xOutApp.CreateItem(olMailItem)
For I = 1 To xRg.Rows.Count
For J = 1 To xRg.Columns.Count
xEmailBody = xEmailBody & " " & xRg.Cells(I, J).Value
Next
xEmailBody = xEmailBody & vbNewLine
Next
xEmailBody = "" & vbLf & vbLf & "" & vbLf & vbLf & xEmailBody & vbNewLine
With xMailOut
.Subject = Worksheets("TDN Generator").Range("A6").Value
.To = ""
.Body = xEmailBody
.Display
'.Send
End With
Set xMailOut = Nothing
Set xOutApp = Nothing
Application.ScreenUpdating = True
End Sub
THIS IS HOW IT PASTES INTO THE EMAIL:
Hello,
Check it out:
Trade 2
Trade Type Grant Number Security Type Shares Sold Shares Exercised
Sell To Cover 12345 Restricted Stock 200
Sell To Cover 12346 Restricted Stock 220
Sell To Cover 12347 Restricted Stock 240
Sell To Cover 12348 Restricted Stock 260
Sell To Cover 12349 Restricted Stock 280
I would like them to all be properly aligned in their said columns.
You can use the Space function to left- or right-pad the values as required.
Option Explicit
Public Sub Test()
Dim colwidth(1 To 5) As Integer
Dim rg As Range
Dim row As Range
Dim col As Integer
Dim val As String
Dim strout As String
' adjust the column widths as required
' you will get an error if the value is
' wider than the column width
colwidth(1) = 13
colwidth(2) = 12
colwidth(3) = 16
colwidth(4) = 12
colwidth(5) = 16
' function is in the worksheet module for convenience
With Me
Set rg = .Range("A1:E6")
For Each row In rg.Rows
' pad left
col = 1
val = row.Cells(1, col)
strout = val & Space(colwidth(col) - Len(val) + 1)
' pad right
col = 2
val = row.Cells(1, col)
strout = strout & Space(colwidth(col) - Len(val)) & val & Space(1)
' pad left
col = 3
val = row.Cells(1, col)
strout = strout & val & Space(colwidth(col) - Len(val) + 1)
' pad right
col = 4
val = row.Cells(1, col)
strout = strout & Space(colwidth(col) - Len(val)) & val & Space(1)
' pad left
col = 5
val = row.Cells(1, col)
strout = strout & val & Space(colwidth(col) - Len(val) + 1)
Debug.Print strout
Next row
End With
End Sub
Data range
Output
Trade Type Grant Number Security Type Shares Sold Shares Exercised
Sell To Cover 12345 Restricted Stock 200
Sell To Cover 12346 Restricted Stock 220
Sell To Cover 12347 Restricted Stock 240
Sell To Cover 12348 Restricted Stock 260
Sell To Cover 12349 Restricted Stock 280
Of course, this will only work with a fixed pitch typeface. Much better to write the data as an html table if the mail client supports that.
Using HTML format:
Sub Send_Email()
Dim xRg As Range
Dim I, J As Long
Dim xAddress As String
Dim xEmailBody As String
Dim xMailOut As Outlook.MailItem
Dim xOutApp As Outlook.Application
On Error Resume Next
Set xRg = Range("A9:E32")
xEmailBody = "Take a look:<br><br>" & HtmlTable(xRg)
Set xOutApp = CreateObject("Outlook.Application")
Set xMailOut = xOutApp.CreateItem(olMailItem)
With xMailOut
.Subject = Worksheets("TDN Generator").Range("A6").Value
.To = ""
.HTMLBody = xEmailBody
.Display
'.Send
End With
Set xMailOut = Nothing
Set xOutApp = Nothing
Application.ScreenUpdating = True
End Sub
Function HtmlTable(rng As Range) As String
Dim s As String, rw As Range, c As Range
s = "<table border=1>"
For Each rw In rng.Rows
s = s & "<tr>"
For Each c In rw.Cells
s = s & "<td>" & c.Value & "</td>"
Next c
s = s & "</tr>"
Next rw
HtmlTable = s & "</table>"
End Function
Have you tried the paste special command?
It's usually CTRL+ALT+V or ALT+E+S to open the paste special dialog.

Impossible Excel-VBA Email Loop

If someone could help me from going insane, my mother would appreciate it.
I have a long list of email addresses (many repeats) with associated Audit Locations. Basically I need to create one email for each email address and populate said email body with a list of all the associated Audit Locations.
e.g.
Column One (Email Address) | Column 2 (Audit Location)
Yoda1#lightside.org | Coruscant
Yoda1#lightside.org | Death Star
Yoda1#lightside.org | Tatooine
Vader#Darkside.org | Death Star
Vader#Darkside.org | Coruscant
Jarjar#terrible.org | Yavin
So far I have created a CommandButton Controlled vba that takes Column One and makes it unique in a new worksheet.
Then I have another sub that creates an email for each unique email address. But I am stuck on the "If...Then" statement. Essentially, I want to add the information in Column 2 (Audit Location) if the Recipient of the email is the email address in Column One and then continue to append to the email body until the email address no longer equals the recipient email address. Any guidance would be huge.
Private Sub CommandButton1_Click()
Call MakeUnique
Call EmailOut
End Sub
Sub MakeUnique()
Dim vaData As Variant
Dim colUnique As Collection
Dim aOutput() As Variant
Dim i As Long
'Put the data in an array
vaData = Sheet1.Range("A:A").Value
'Create a new collection
Set colUnique = New Collection
'Loop through the data
For i = LBound(vaData, 1) To UBound(vaData, 1)
'Collections can't have duplicate keys, so try to
'add each item to the collection ignoring errors.
'Only unique items will be added
On Error Resume Next
colUnique.Add vaData(i, 1), CStr(vaData(i, 1))
On Error GoTo 0
Next i
'size an array to write out to the sheet
ReDim aOutput(1 To colUnique.Count, 1 To 1)
'Loop through the collection and fill the output array
For i = 1 To colUnique.Count
aOutput(i, 1) = colUnique.Item(i)
Next i
'Write the unique values to column B
Sheets.Add.Name = "Unique"
ActiveSheet.Range("A1").Resize(UBound(aOutput, 1), UBound(aOutput, 2)).Value = aOutput
End Sub
Sub EmailOut()
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
On Error Resume Next
Dim cell As Range
For Each cell In Worksheets("Unique").Columns("a").Cells.SpecialCells(xlCellTypeConstants)
recip = cell.Value
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
For Each org In Columns("b").Cells.SpecialCells(xlCellTypeConstants)
If org.Value Like recip Then
xMailBody = "Body content" & vbNewLine & vbNewLine & _
"This is line 1" & " " & cell.Offset(0, 3).Value & vbNewLine & _
[B5] & vbNewLine & _
"This is line 2"
End If
Next org
On Error Resume Next
With xOutMail
.To = recip
.CC = ""
.BCC = ""
.Subject = cell.Offset(0, 2).Value & " " & cell.Offset(0, 3).Value & " " & "Remittance Advice"
.Body = xMailBody
.Display 'or use .Send
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
Next
End Sub
Based on your example I quickly wrote the following:
Option Explicit
Public Sub SendEmails()
Dim dictEmailData As Object
Dim CurrentWorkBook As Workbook
Dim WrkSht As Worksheet
Dim rngToLookUp As Range
Dim lngLastRow As Long, i As Long
Dim arryEmailData As Variant
Dim objOutlookApp As Object, objOutlookEmail As Object
Dim varKey As Variant
Application.ScreenUpdating = False
Set CurrentWorkBook = Workbooks("SomeWBName")
Set WrkSht = CurrentWorkBook.Worksheets("SomeWSName")
lngLastRow = WrkSht.Cells(WrkSht.Rows.Count, "A").End(xlUp).Row 'Find last row with data
Set rngToLookUp = WrkSht.Range("A2:B" & lngLastRow) 'set range for last row of data
arryEmailData = rngToLookUp.Value2 'Get the email data from the sheet into an array
Set dictEmailData = CreateObject("Scripting.Dictionary") 'set the dicitonary object
On Error GoTo CleanFail
For i = LBound(arryEmailData, 1) To UBound(arryEmailData, 1)
varKey = UCase(Trim(arryEmailData(i, 1)))
If Not dictEmailData.Exists(varKey) Then
dictEmailData(varKey) = vbNewLine & vbNewLine & Trim(arryEmailData(i, 2))
Else
dictEmailData(varKey) = dictEmailData(varKey) & vbNewLine & Trim(arryEmailData(i, 2))
End If
varKey = Empty
Next i
'for each unique key in the dicitonary
'get the corresponding item
'created in the loop above
Set objOutlookApp = CreateObject("Outlook.Application") 'set the outlook object
Dim Msg As String, MailBody As String
For Each varKey In dictEmailData.Keys
Msg = dictEmailData.Item(varKey)
Set objOutlookEmail = objOutlookApp.CreateItem(0)
MailBody = "Dear Colleague," & Msg
With objOutlookEmail
.To = varKey
.Subject = "Remittance Advice"
.Body = MailBody
.Send
End With
Set objOutlookEmail = Nothing
Msg = Empty: MailBody = Empty
Next
MsgBox "All Emails have been sent", vbInformation
CleanExit:
Set objOutlookApp = Nothing
Application.ScreenUpdating = True
Exit Sub
CleanFail:
Resume CleanExit
End Sub
Add the first occurrence of a varKey = email address to the dictionary dictEmailData along with its corresponding item dictEmailData(varKey) = Email body. On the next occurrence of the email address, append to the Email body. Once the dictionary is built, loop through it and send the emails
Printing to the immediate window yields:

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 Based on Cell Value within a Loop

I have a sample sheet
I have a module that runs through the list in a loop within another loop, checking for duplicate names and then grouping the names together to send an email with an attachment based on Column D (Division).
Sample 4 would get one email with 3 attachments.
I have been asked to build in the ability to exclude people based on a value (I chose yes or no, column C) before running the module.
Reason being that if the list is long (over 1000 names) to set it before generating the emails. I would build in a trigger to set that value, but it is apparently an arbitrary decision made by the senders in a dept.
I have tried to build an IF statement into the loop as shown below but it is as if the If statement is coming out as not being true (I stepped through).
Which means all the With Outmail objects will not work.
I was able to get it to work by using the if statement with a for/next setup on its own (no loops), but cannot get it to work with the loop, which is the more important piece.
Here is the main piece of code. The main loop and then the if statement to account for the yes or no values:
Do While r <= rng.Rows.Count
If rng.Cells(r, 3).Value Like "?*#?*.?*" And LCase(rng.Cells(r, 3)) = "yes" Then
Set OutMail = OutApp.CreateItem(0)
End If
And here is the full sub:
Sub EmailDivisions()
Dim OutApp As Object
Dim OutMail As Object
Dim cell, lookrng As Range
Dim strDir As String
Dim strFilename As String
Dim sigString As String
Dim strBody As String
Dim strName As Variant
Dim strName1 As Variant
Dim strDept As Variant
Dim strName2 As String
Dim strName3 As Variant
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
sigString = Environ("appdata") & _
"\Microsoft\Signatures\Divisions.htm"
If Dir(sigString) <> "" Then
signature = GetBoiler(sigString)
Else
signature = ""
End If
Set rng = ActiveSheet.UsedRange
r = 2
Do While r <= rng.Rows.Count
If rng.Cells(r, 3).Value Like "?*#?*.?*" And LCase(rng.Cells(r, 3)) = "yes" Then
Set OutMail = OutApp.CreateItem(0)
End If
Set strName = rng.Cells(r, 1)
Set strDept = rng.Cells(r, 4)
strName2 = Left(strName, InStr(strName & " ", " ") - 1)
With OutMail
strFilename = Dir("\\Divisons\1a*" & strDept & "*")
.SentOnBehalfOfName = "divisionalsend#xyz.org"
.To = rng.Cells(r, 2).Value
.Subject = "Monthly Divisional Report for " & strDept
.HTMLBody = "<Font Face=calibri>" & "Dear " & strName2 & ",<br><br>" & signature
.Attachments.Add strDir & strFilename
'See if the next row is for the same sender. If so, process that
'row as well. And then keep doing it until no more rows match
Do While rng.Cells(r, 2).Value = rng.Cells(r + 1, 2)
r = r + 1
Set strDept = rng.Cells(r, 4)
strfilename1 = Dir("\\Divisions\1a*" & strDept & "*")
.Subject = "Monthly Divisional Report for Your Departments"
.Attachments.Add strDir & strfilename1
Loop
.Display
End With
Set OutMail = Nothing
r = r + 1
Loop
Set OutApp = 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
Figured it out, here is the final sub:
Sub EmailDivisions()
Dim OutApp As Object
Dim OutMail As Object
Dim cell, lookrng As Range
Dim strDir As String
Dim strFilename As String
Dim sigString As String
Dim strBody As String
Dim strName As Variant
Dim strName1 As Variant
Dim strDept As Variant
Dim strName2 As String
Dim strName3 As Variant
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
sigString = Environ("appdata") & _
"\Microsoft\Signatures\Divisions.htm"
If Dir(sigString) <> "" Then
signature = GetBoiler(sigString)
Else
signature = ""
End If
Set rng = ActiveSheet.UsedRange
r = 2
Do While r <= rng.Rows.Count
Debug.Print LCase(rng.Cells(r, 2))
If Cells(r, 2).Value Like "?*#?*.?*" And LCase(Cells(r, 3)) = "yes" Then
Set OutMail = OutApp.CreateItem(0)
ElseIf Cells(r, 2).Value Like "?*#?*.?*" And LCase(Cells(r, 3)) = "no" Then GoTo ContinueLoop
End If
Set strName = Cells(r, 1)
Set strDept = Cells(r, 4)
strName2 = Left(strName, InStr(strName & " ", " ") - 1)
With OutMail
strFilename = Dir("\\Divisons\1a*" & strDept & "*")
.SentOnBehalfOfName = "divisionalsend#xyz.org"
.To = Cells(r, 2).Value
.Subject = "Monthly Divisional Report for " & strDept
.HTMLBody = "<Font Face=calibri>" & "Dear " & strName2 & ",<br><br>" & signature
.Attachments.Add strDir & strFilename
.display
'See if the next row is for the same sender. If so, process that
'row as well. And then keep doing it until no more rows match
Do While rng.Cells(r, 2).Value = rng.Cells(r + 1, 2)
r = r + 1
Set strDept = Cells(r, 4)
strfilename1 = Dir("\\Divisions\1a*" & strDept & "*")
.Subject = "Monthly Divisional Report for Your Departments"
.Attachments.Add strDir & strfilename1
.Display
ContinueLoop:
Loop
End With
Set OutMail = Nothing
r = r + 1
Loop
Set OutApp = 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