I have a VBA macro to send e-mails to different recipients, but now I want to add attachments. The problem is the attachments paths are in an excel table and it varies according to the customer. I.e. customer A has 3 lines in the table, each with a different attachment, cust B has 5 lines, and so on.
Anyone knows how to vlookup it and get all possible files paths? Here follows my current code without attachments:
Sub Controle_de_orçamentos()
response = MsgBox("Deseja enviar as cobranças?", vbYesNo)
If response = vbNo Then
MsgBox ("Então tchau")
Exit Sub
End If
Dim OutApp As Object
Dim OutMail As Object
Dim rng As Range
Dim Ash As Worksheet
Dim Cws As Worksheet
Dim Rcount As Long
Dim Rnum As Long
Dim FilterRange As Range
Dim FieldNum As Integer
Dim mailAddress As String
On Error GoTo cleanup
Set OutApp = CreateObject("Outlook.Application")
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Set filter sheet, you can also use Sheets("MySheet")
Set Ash = ActiveSheet
'Set filter range and filter column (Column with names)
Set FilterRange = Ash.Range("A1:H" & Ash.Rows.Count)
FieldNum = 1 'Filter column = A because the filter range start in A
'Add a worksheet for the unique list and copy the unique list in A1
Set Cws = Worksheets.Add
FilterRange.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Cws.Range("A1"), _
CriteriaRange:="", Unique:=True
'Count of the unique values + the header cell
Rcount = Application.WorksheetFunction.CountA(Cws.Columns(1))
'If there are unique values start the loop
If Rcount >= 2 Then
For Rnum = 2 To Rcount
'Filter the FilterRange on the FieldNum column
FilterRange.AutoFilter Field:=FieldNum, _
Criteria1:=Cws.Cells(Rnum, 1).Value
'Look for the mail address in the MailInfo worksheet
mailAddress = ""
On Error Resume Next
mailAddress = Application.WorksheetFunction. _
VLookup(Cws.Cells(Rnum, 1).Value, _
Worksheets("Mailinfo").Range("A1:B" & _
Worksheets("Mailinfo").Rows.Count), 2, False)
On Error GoTo 0
If mailAddress <> "" Then
With Ash.AutoFilter.Range
On Error Resume Next
Set rng = .SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = mailAddress
.Subject = "Orçamentos aguardando aprovação - Indi Empilhadeiras"
.HTMLBody = "Prezados(as), boa tarde!<br>" & _
"Poderiam, por gentileza, informar se os orçamentos abaixo estão aprovados?" & RangetoHTML(rng) & _
"<br>Obrigado!<br>" & _
"Denis Scalco<br>" & _
"(15) 98145-0856"
.Display 'Or use Send
.Send
End With
On Error GoTo 0
Set OutMail = Nothing
End If
'Close AutoFilter
Ash.AutoFilterMode = False
Next Rnum
End If
cleanup:
Set OutApp = Nothing
Application.DisplayAlerts = False
Cws.Delete
Application.DisplayAlerts = True
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
To filter the files for a specific customer you may want to use standard or advanced filtering:
https://support.microsoft.com/en-gb/office/filter-by-using-advanced-criteria-4c9222fe-8529-4cd7-a898-3f16abdff32b
read file names of attachments from range: see below code proposal
add attachments
How to add an attachment to an email using VBA in Excel
Option Explicit
Sub test()
Dim myA(20) As String
Dim myCt As Integer
Dim NrFiles As Integer
Call ReadAttachments(myA, Range("H1:H10"), False, NrFiles)
For myCt = 1 To NrFiles
Debug.Print myCt, myA(myCt)
Next myCt
End Sub
Sub ReadAttachments(ByRef myAttachments() As String, myRange As Range, _
hasHeader As Boolean, FileCt As Integer)
Dim myCell As Range
Dim iCt As Integer
For Each myCell In myRange
If iCt <> 0 Then
If myCell.Value <> "" Then
myAttachments(iCt) = myCell.Value
FileCt = FileCt + 1
End If
End If
iCt = iCt + 1
Next myCell
End Sub
Related
I am trying to:
Send an email to each user in a list.
(MailInfo - sheet1 - has two columns. A = Users, B = Email addresses)
Attach rows from four sheets with rows relevant to them (Columns A:H)
(Users are listed in column H in the other 4 sheets. Currently just 4 sheets that have ranges)
I want to loop through the agent list in Sheet 1 and then add the tables into the body of the email with just the rows that are relevant to them.
The code below will open an email for each user with complete tables.
I was able to get the following (from Ron de Bruin's documentation), to open emails for each row in EmailList 1 (I renamed it to MailInfo) and had to add column B to add the mail addresses.
I need to figure out the filtering of the values in the ranges to each user in column A in MailInfo.
Sub Send_Row_Or_Rows_1()
'For Tips see: http://www.rondebruin.nl/win/winmail/Outlook/tips.htm
'Don't forget to copy the function RangetoHTML in the module.
'Working in Excel 2000-2016
Dim OutApp As Object
Dim OutMail As Object
Dim rng As Range
Dim rng1 As Range
Dim Ash As Worksheet
Dim Cws As Worksheet
Dim Rcount As Long
Dim Rnum As Long
Dim FilterRange As Range
Dim FieldNum As Integer
Dim mailAddress As String
On Error GoTo cleanup
Set OutApp = CreateObject("Outlook.Application")
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Set filter sheet, you can also use Sheets("MySheet")
Set Ash = ActiveSheet
'Set filter range and filter column (Column with names)
Set FilterRange = Ash.Range("A1:H" & Ash.Rows.Count)
FieldNum = 1 'Filter column = A because the filter range start in A
'Add a worksheet for the unique list and copy the unique list in A1
Set Cws = Worksheets.Add
FilterRange.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Cws.Range("A1"), _
CriteriaRange:="", Unique:=True
'Count of the unique values + the header cell
Rcount = Application.WorksheetFunction.CountA(Cws.Columns(1))
'If there are unique values start the loop
If Rcount >= 2 Then
For Rnum = 2 To Rcount
'Filter the FilterRange on the FieldNum column
FilterRange.AutoFilter Field:=FieldNum, _
Criteria1:=Cws.Cells(Rnum, 1).Value
'Look for the mail address in the MailInfo worksheet
mailAddress = ""
On Error Resume Next
mailAddress = Application.WorksheetFunction. _
VLookup(Cws.Cells(Rnum, 1).Value, _
Worksheets("Mailinfo").Range("A1:B" & _
Worksheets("Mailinfo").Rows.Count), 2, False)
On Error GoTo 0
If mailAddress <> "" Then
With Ash.AutoFilter.Range
On Error Resume Next
Set rng = Sheets("SampleTable1").Range("A1:H10").SpecialCells(xlCellTypeVisible)
Set rng1 = Sheets("SampleTable2").Range("A1:H10").SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
Set OutMail = OutApp.createitem(0)
On Error Resume Next
With OutMail
.To = mailAddress
.Subject = "Test mail"
.HTMLBody = RangetoHTML(rng) & "<br>" & RangetoHTML(rng1)
.display 'Or use Send
End With
On Error GoTo 0
Set OutMail = Nothing
End If
'Close AutoFilter
Ash.AutoFilterMode = False
Next Rnum
End If
cleanup:
Set OutApp = Nothing
Application.DisplayAlerts = False
Cws.Delete
Application.DisplayAlerts = True
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
Function RangetoHTML(rng As Range)
' Changed by Ron de Bruin 28-Oct-2006
' Working in Office 2000-2016
Dim fso As Object
Dim ts As Object
Dim TempFile As String
Dim TempWB As Workbook
TempFile = Environ$("temp") & "/" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
'Copy the range and create a new workbook to past the data in
rng.Copy
Set TempWB = Workbooks.Add(1)
With TempWB.Sheets(1)
.Cells(1).PasteSpecial Paste:=8
.Cells(1).PasteSpecial xlPasteValues, , False, False
.Cells(1).PasteSpecial xlPasteFormats, , False, False
.Cells(1).Select
Application.CutCopyMode = False
On Error Resume Next
.DrawingObjects.Visible = True
.DrawingObjects.Delete
On Error GoTo 0
End With
'Publish the sheet to a htm file
With TempWB.PublishObjects.Add( _
SourceType:=xlSourceRange, _
filename:=TempFile, _
Sheet:=TempWB.Sheets(1).Name, _
Source:=TempWB.Sheets(1).UsedRange.Address, _
HtmlType:=xlHtmlStatic)
.Publish (True)
End With
'Read all data from the htm file into RangetoHTML
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(TempFile).OpenAsTextStream(1, -2)
RangetoHTML = ts.ReadAll
ts.Close
RangetoHTML = Replace(RangetoHTML, "align=center x:publishsource=", _
"align=left x:publishsource=")
'Close TempWB
TempWB.Close savechanges:=False
'Delete the htm file we used in this function
Kill TempFile
Set ts = Nothing
Set fso = Nothing
Set TempWB = Nothing
End Function
For each user, consolidate the filtered data from the 4 sheets onto 1 temporary sheet and then use RangeToHtml.
Option Explicit
Sub Send_Row_Or_Rows_1()
Dim wb As Workbook
Dim wsInfo As Worksheet, ws As Worksheet, wsTmp As Worksheet
Dim i As Long, lastrow As Long
Set wb = ThisWorkbook
' sheets to copy
Dim data(3) As Worksheet
Set data(0) = wb.Sheets("SampleTable1")
Set data(1) = wb.Sheets("SampleTable2")
Set data(2) = wb.Sheets("SampleTable3")
Set data(3) = wb.Sheets("SampleTable4")
' add a temporary sheet
Application.DisplayAlerts = False
For Each ws In Sheets
If ws.name = "~tmp" Then ws.Delete
Next
Set wsTmp = Sheets.Add
wsTmp.name = "~tmp"
Application.DisplayAlerts = True
Dim rngCopy As Range
Dim sName As String, sAddr As String
Dim n As Long, k As Long, r As Long
' outlook
Dim appOut As Object, OutMail As Object
Set appOut = CreateObject("Outlook.Application")
' scan users
Set wsInfo = wb.Sheets("Mail Info")
With wsInfo
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
' for each user
For i = 2 To lastrow
sName = Trim(.Cells(i, "A"))
sAddr = Trim(.Cells(i, "B"))
r = 1
wsTmp.Cells.Clear
' consolidate each sheet on tmp sheet
For k = 0 To UBound(data)
Set ws = data(k)
' filter on name in col H 8
With ws.UsedRange
.AutoFilter
.AutoFilter 8, sName ' col H
Set rngCopy = .SpecialCells(xlCellTypeVisible)
rngCopy.Copy wsTmp.Cells(r, 1)
If r > 1 Then wsTmp.Rows(r).Delete ' leave 1 header
r = wsTmp.Cells(ws.Rows.Count, "A").End(xlUp).Row + 2 ' leave blank line
.AutoFilter
End With
Next
' email sheet
If r > 1 Then
Set OutMail = appOut.createitem(0)
With OutMail
.To = sAddr
.Subject = "Test Mail to " & sName
.HTMLBody = RangetoHTML(wsTmp.UsedRange)
.display 'Or use Send
End With
Set OutMail = Nothing
n = n + 1
End If
Next
End With
Application.DisplayAlerts = False
'ws.Sheets("~tmp").Delete
Application.DisplayAlerts = True
MsgBox n & " emails sent", vbInformation
End Sub
I'm currently working on a vba excel macro that filters particular rows (based on values in one column), then copies particular columns from the filtered rows & paste them as a table into the outlook email body.
I'd like the table to be pasted after the text in the email body. However, it seems that the table is the only thing that is in the mail body & I can't put the text before the table.
Would much appreciate your advice on how to display the text in the email body before the pasted table. My current: "OutMail.Body = "The body text I want to put before the table" does not work.
EDIT 1 = adjusted according to CDP1802 + added moving rows to archive feature
Code:
Option Explicit Public ws As Worksheet
Public ol As ListObject
Public olRng As Range
Sub CopyTableToEmail()
Dim olCol As Integer, datCol As Integer
Set ws = Sheets("Test1")
Set ol = ws.ListObjects("TbClient")
Set olRng = ol.Range
'remove table filters
ol.ShowAutoFilter = False
'get Valid column
olCol = ol.ListColumns("Valid").Index
'filter table
ol.Range.AutoFilter field:=olCol, Criteria1:="<0", Operator:=xlOr
'select table to copy
Set olRng = ol.Range
'create mail
Call CreateMail
'Change values on Deactivation e-mail sent column
datCol = ol.ListColumns("Deactivation e-mail sent").Index
ol.ListColumns(datCol).DataBodyRange.SpecialCells(xlCellTypeVisible).Value = Range("H1")
'clear table filters
ol.AutoFilter.ShowAllData
'Move rows to the Archive
Call MoveRows
End Sub
Sub CreateMail()
On Error GoTo errHandler
Dim olCol As Integer, rCell As Range, addRng As Range
Dim mailBcc As String, mailCC As String
'Outlook
Dim OutApp As Object, OutInsp As Object, OutMail As Object
'Declare Word Variables
Dim oWrdDoc As Word.Document, oWdEditor As Word.Editors
Dim sText As String
'Range of mail adresses
olCol = ol.ListColumns("Requestor (email)").Index
Set addRng = ol.ListColumns(olCol).DataBodyRange.SpecialCells(xlCellTypeVisible)
'get the mail addresses
For Each rCell In addRng
mailBcc = mailBcc & rCell.Value & ";"
mailCC = mailCC & rCell.Offset(0, 1).Value & ";"
Next rCell
' copy table colmns B,J,L
ws.Columns.Hidden = False
ws.Range("A:A,C:I,K:K,M:Z").EntireColumn.Hidden = True
olRng.Copy
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
Dim signature As String
With OutMail
.display ' or send
signature = .body
.cc = mailCC
.Bcc = mailBcc
.Subject = "Openings Tracker"
End With
' Text
sText = "Ladies and gentlemen," & vbCrLf & vbCrLf
'Get the Active Inspector
'Get the document within the inspector
Set OutInsp = OutMail.GetInspector
Set oWrdDoc = OutInsp.WordEditor
With oWrdDoc
.Content.Delete
.Paragraphs.Add.Range.Text = sText
.Paragraphs.Add.Range.Paste
.Paragraphs.Add.Range.Text = signature
End With
ws.Columns.Hidden = False
Application.CutCopyMode = False
exitRoutine:
'clear
Set OutMail = Nothing
Set OutApp = Nothing
Set ws = Nothing
Exit Sub
errHandler:
'Open immediate window to see the error
Debug.Print Err.Number, Err.Description
Resume exitRoutine
End Sub
Sub MoveRows()
Dim xRg As Range
Dim xCell As Range
Dim A As Long
Dim B As Long
Dim O As Long
A = Worksheets("Test1").UsedRange.Rows.Count
B = Worksheets("Archive").UsedRange.Rows.Count
If B = 1 Then
If Application.WorksheetFunction.CountA(Worksheets("Archive").UsedRange) = 0 Then B = 0
End If
Set xRg = Worksheets("Test1").Range("O1:O" & A)
On Error Resume Next
Application.ScreenUpdating = False
For O = 1 To xRg.Count
If CStr(xRg(O).Value) = "OK" Then
xRg(O).EntireRow.Copy Destination:=Worksheets("Archive").Range("A" & B + 1)
xRg(O).EntireRow.Delete
If CStr(xRg(O).Value) = "OK" Then
O = O - 1
End If
B = B + 1
End If
Next
Application.ScreenUpdating = True
End Sub
Add a paragraph to the word document.
update1 - Filter table, add signature to the end.
update2 - Show only columns B J L
update3 - Added AchiveRows()
Option Explicit
Public ws As Worksheet
Public ol As ListObject
Public olRng As Range
Sub CopyTableToEmail()
Dim olCol As Integer, datCol As Integer
Set ws = Sheets("Test1")
Set ol = ws.ListObjects("tbClient")
Set olRng = ol.Range
'remove table filters
ol.ShowAutoFilter = False
'get Valid column
olCol = ol.ListColumns("Valid").Index
'filter table
ol.Range.AutoFilter field:=olCol, Criteria1:="<0", Operator:=xlOr
'select table to copy
Set olRng = ol.Range
'create mail
Call CreateMail
'clear table filters
ol.AutoFilter.ShowAllData
End Sub
Sub CreateMail()
On Error GoTo errHandler
Dim olCol As Integer, rCell As Range, addRng As Range
Dim mailBcc As String, mailCC As String
' Outlook
Dim OutApp As Object, OutInsp As Object, OutMail As Object
'Declare Word Variables
Dim oWrdDoc As Word.Document, oWdEditor As Word.Editors
Dim sText As String
'Range of mail adresses
olCol = ol.ListColumns("Requestor email").Index
Set addRng = ol.ListColumns(olCol).DataBodyRange.SpecialCells(xlCellTypeVisible)
'get the mail addresses
For Each rCell In addRng
mailBcc = mailBcc & rCell.Value & ";"
mailCC = mailCC & rCell.Offset(0, 1).Value & ";"
Next rCell
' copy table colmns B,J,L
ws.Columns.Hidden = False
ws.Range("A:A,C:I,K:K,M:Z").EntireColumn.Hidden = True
olRng.Copy
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
Dim signature As String
With OutMail
.display ' or send
signature = .body
.cc = mailCC
.Bcc = mailBcc
.Subject = "Generic Subject"
End With
' Text
sText = "The body text I want to put before the table" & vbCrLf & vbCrLf
'Get the Active Inspector
'Get the document within the inspector
Set OutInsp = OutMail.GetInspector
Set oWrdDoc = OutInsp.WordEditor
With oWrdDoc
.Content.Delete
.Paragraphs.Add.Range.Text = sText
.Paragraphs.Add.Range.Paste
.Paragraphs.Add.Range.Text = signature
End With
ws.Columns.Hidden = False
Application.CutCopyMode = False
exitRoutine:
'clear
Set OutMail = Nothing
Set OutApp = Nothing
Set ws = Nothing
Exit Sub
errHandler:
'Open immediate window to see the error
Debug.Print Err.Number, Err.Description
Resume exitRoutine
End Sub
Sub ArchiveRows()
Dim ol As ListObject, rng As Range
Dim r As Long, olCol As Long, n As Long
Set ol = Sheets("Test1").ListObjects("tbClient")
olCol = ol.ListColumns("Valid").Index
With ol.DataBodyRange
For r = 1 To .Rows.Count
If UCase(Trim(.Cells(r, olCol).Value)) = "OK" Then
If rng Is Nothing Then
Set rng = .Rows(r)
Else
Set rng = Union(rng, .Rows(r))
End If
End If
Next
End With
If rng Is Nothing Then
n = 0
Else
n = rng.Rows.Count
With Sheets("Archive")
rng.Copy
With .Cells(.Rows.Count, 1).End(xlUp).Offset(1)
.PasteSpecial xlPasteValues
End With
End With
rng.Rows.Delete
Application.CutCopyMode = False
End If
MsgBox n & " rows moved to Archive and deleted"
End Sub
I am trying to automate the email process which we have been sending to various stack holders.
I wanted to filter the column D based on company code and send out the email to the people listed in O column ( the email should not be duplicated), and also need to include CC (without duplicates)
Below is the VBA which am trying, but could not include the TO and CC.
Sub Send_Row_Or_Rows_2()
Dim OutApp As Object
Dim OutMail As Object
Dim rng As Range
Dim Ash As Worksheet
Dim Cws As Worksheet
Dim Rcount As Long
Dim Rnum As Long
Dim FilterRange As Range
Dim FieldNum As Integer
Dim StrBody As String
Dim StrBody2 As String
Dim FileToAttach As String
Dim RngTo As Range
Set RngTo = Ash.Columns("H").Offset(1, 0).SpecialCells(xlCellTypeVisible)
StrBody = "<BODY style=font-size:11pt;font-family:Calibri>Dear Approver,<p>Please be informed that below invoices are waiting for your approval in BasWare for more than 10 days. Please check them and take action accordingly as soon as possible.</BODY>"
'On Error GoTo cleanup
Set OutApp = CreateObject("Outlook.Application")
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Set filter sheet, you can also use Sheets("MySheet")
Set Ash = Worksheets("rawdata")
'Set filter range and filter column (column with e-mail addresses)
Set FilterRange = Ash.Range("A4:M" & Ash.Rows.Count)
FieldNum = 4 'Filter column = D because the filter range start in column A
'Add a worksheet for the unique list and copy the unique list in A1
Set Cws = Worksheets.Add
FilterRange.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Cws.Range("A1"), _
CriteriaRange:="", Unique:=True
'Count of the unique values + the header cell
Rcount = Application.WorksheetFunction.CountA(Cws.Columns(1))
'If there are unique values start the loop
If Rcount >= 2 Then
For Rnum = 2 To Rcount
'Filter the FilterRange on the FieldNum column
FilterRange.AutoFilter Field:=FieldNum, _
Criteria1:=Cws.Cells(Rnum, 1).Value
'If the unique value is a mail addres create a mail
If Cws.Cells(Rnum, 1).Value Like "?*?*?*" Then
With Ash.AutoFilter.Range
On Error Resume Next
Set rng = .SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = Ash.Cells(Rnum, 15).Value
.SentOnBehalfOfName = "CDM_Basware_Administration#esab.com"
.CC = sCC
.Subject = "Reminder - Pending Invoices - More than 10 days"
.HTMLBody = StrBody & RangetoHTML(rng) & signature
FileToAttach = "C:\Users\gyousz\Desktop\Weekly_Customer CL3 and PT Control file_May_2018"
.Display
End With
On Error GoTo 0
Set OutMail = Nothing
End If
'Close AutoFilter
Ash.AutoFilterMode = False
Next Rnum
End If
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
Function RangetoHTML(rng As Range)
Dim fso As Object
Dim ts As Object
Dim TempFile As String
Dim TempWB As Workbook
TempFile = Environ$("temp") & "/" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
'Copy the range and create a new workbook to paste the data in
rng.Copy
Set TempWB = Workbooks.Add(1)
With TempWB.Sheets(1)
.Cells(1).PasteSpecial Paste:=8
.Cells(1).PasteSpecial xlPasteValues, , False, False
.Cells(1).PasteSpecial xlPasteFormats, , False, False
.Cells(1).Select
Application.CutCopyMode = False
On Error Resume Next
.DrawingObjects.Visible = True
.DrawingObjects.Delete
On Error GoTo 0
End With
'Publish the sheet to a htm file
With TempWB.PublishObjects.Add( _
SourceType:=xlSourceRange, _
Filename:=TempFile, _
Sheet:=TempWB.Sheets(1).Name, _
Source:=TempWB.Sheets(1).UsedRange.Address, _
HtmlType:=xlHtmlStatic)
.Publish (True)
End With
'Read all data from the htm file into RangetoHTML
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(TempFile).OpenAsTextStream(1, -2)
RangetoHTML = ts.ReadAll
ts.Close
RangetoHTML = Replace(RangetoHTML, "align=center x:publishsource=", _
"align=left x:publishsource=")
'Close TempWB
TempWB.Close savechanges:=False
'Delete the htm file we used in this function
Kill TempFile
Set ts = Nothing
Set fso = Nothing
Set TempWB = Nothing
End Function
Please divide your codes into separate functions:
One for getting recipients
One to send email
I have recreated your workbook. Code below would do the ff:
Get all company codes first
Filter list by company codes
Get TO and CC list
Send email
Only modification left here is creating another function for sending email (and pass the variables).
Sub Send_Row_Or_Rows_2()
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
On Error GoTo ErrorHandler
' Initialization
' ==================================================
Dim shtRec As Worksheet: Set shtRec = ThisWorkbook.Sheets("rawdata")
Dim intLastRow As Long, intLastCol As Long ' for end cell
Dim i As Long, j As Long, k As Long, rngCell As Range ' for loops
Dim rngFilter As Range ' filter range
Dim strEmailTO As String, strEmailCC As String ' recipients
Dim arrCoCd() As String ' company codes
Dim arrEmailTO() As String ' TO recipients
Dim arrEmailCC() As String ' CC recipients
Dim arrEmailRec() As String, strEmailRec As String ' temporary variables
' Get Recipient header column indexes
Dim intRowHead As Integer: intRowHead = 4 ' header row
Dim intColCoCd As Integer: intColCoCd = 1 ' company code column
Dim intColTo As Integer: intColTo = 3 ' TO column
Dim intColCc As Integer: intColCc = 4 ' CC column
' Filter Recipients by Company Code
' ==================================================
With shtRec
' Remove filter
If Not .AutoFilter Is Nothing Then .AutoFilterMode = False
' Get end cell
With .Cells.SpecialCells(xlCellTypeLastCell)
intLastRow = .Row
intLastCol = .Column
End With
' Add filter
Set rngFilter = .Range(Cells(intRowHead, 1), Cells(intLastRow, intLastCol))
rngFilter.AutoFilter
' Get list of company codes
' =========================
ReDim arrCoCd(1 To intLastRow)
For i = (intRowHead + 1) To intLastRow ' exclude header
With .Cells(i, intColCoCd)
If .Value <> vbNullString Then
k = k + 1
arrCoCd(k) = VBA.Trim(.Value)
End If
End With
Next i
' Reset variable
k = 0
' Get unique values
' =========================
arrCoCd = FnStrUniqueArray(arrCoCd)
' Filter by Company Code
For i = LBound(arrCoCd) To UBound(arrCoCd)
If arrCoCd(i) <> vbNullString Then
rngFilter.AutoFilter Field:=intColCoCd, Criteria1:="=" & arrCoCd(i)
While Not Application.CalculationState = xlDone: DoEvents: Wend
' Get list only if with results
If .AutoFilter.Range.Columns(intColCoCd).SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
Dim strRng As String
' Get TO list
' =========================
' Loop each visible cell in TO column
k = 0
strRng = .AutoFilter.Range.Offset(1, 0).Resize(.AutoFilter.Range.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Columns(intColTo).Address(False, False)
For Each rngCell In .Range(strRng)
' Remove spaces
strEmailRec = VBA.Trim(VBA.Replace(rngCell.Value, " ", ""))
' Get email addresses
arrEmailRec = VBA.Split(strEmailRec, ";")
' Add email addresses to list
If k = 0 Then k = k + 1 Else k = UBound(arrEmailTO) + 1
ReDim Preserve arrEmailTO(1 To k)
For j = LBound(arrEmailRec) To UBound(arrEmailRec)
arrEmailTO(k) = arrEmailRec(j)
Next j
' Remove duplicates in list
arrEmailTO = FnStrUniqueArray(arrEmailTO)
' Reset variables
strEmailRec = vbNullString
Erase arrEmailRec
Next rngCell
' Get CC list
' =========================
' Loop each visible cell in CC column
k = 0
strRng = .AutoFilter.Range.Offset(1, 0).Resize(.AutoFilter.Range.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Columns(intColCc).Address(False, False)
For Each rngCell In .Range(strRng)
' Remove spaces
strEmailRec = VBA.Trim(VBA.Replace(rngCell.Value, " ", ""))
' Get email addresses
arrEmailRec = VBA.Split(strEmailRec, ";")
' Add email addresses to list
If k = 0 Then k = k + 1 Else k = UBound(arrEmailCC) + 1
ReDim Preserve arrEmailCC(1 To k)
For j = LBound(arrEmailRec) To UBound(arrEmailRec)
arrEmailCC(k) = arrEmailRec(j)
Next j
' Remove duplicates in list
arrEmailCC = FnStrUniqueArray(arrEmailCC)
' Reset variables
strEmailRec = vbNullString
Erase arrEmailRec
Next rngCell
End If
' Join recipients list
strEmailTO = VBA.Join(arrEmailTO, ";")
strEmailCC = VBA.Join(arrEmailCC, ";")
' Send email
Set OutMail = OutApp.CreateItem(0)
Dim strSubject As String: strSubject = "Reminder - Pending Invoices - More than 10 days"
Dim strAttachment As String: strAttachment = "C:\Users\gyousz\Desktop\Weekly_Customer CL3 and PT Control file_May_2018"
Dim strSendOnBehalf As String: strSendOnBehalf = "CDM_Basware_Administration#esab.com"
On Error Resume Next
With OutMail
.To = strEmailTO
.SentOnBehalfOfName = strSendOnBehalf
.CC = strEmailCC
.Subject = strSubject
.HTMLBody = StrBody & RangetoHTML(rng) & signature
.Attachments.Add strAttachment
.Display
End With
On Error GoTo 0
' Reset variables
Erase arrEmailTO
Erase arrEmailCC
End If
Next i
End With
ErrorHandler:
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
Here is the code to remove duplicates in arrays.
Reference:vba get unique values from array
Function FnStrUniqueArray(aTmpArray() As String)
Dim ctr As Long, cTmpCollection As New Collection, cTmpCollect
For Each cTmpCollect In aTmpArray
cTmpCollection.Add cTmpCollect, cTmpCollect
Next
' convert collection to array
ReDim aTmpArray(1 To cTmpCollection.Count)
For ctr = 1 To cTmpCollection.Count
aTmpArray(ctr) = cTmpCollection(ctr)
Next ctr
Set cTmpCollection = Nothing
FnStrUniqueArray = aTmpArray
End Function
I guess I would like to know what your results look like now but you could do the following -- you would need to sort your sheet by Company
DIM TheToList, TheCCList, CurrRow
CurrRow = 1
Do until --end of the sheet is reached ---
TheToList = ""
TheCCList = ""
if cells(CurrRow, 4) = cells(CurrRow-1,4) then ' same company
' I was wrong >>> if instr(1,TheCCList,cells(CurrRow,15)) = 0 then ' diff TO
if instr(1,TheToList,cells(CurrRow,15)) = 0 then ' diff TO
TheToList = TheToList & cells(CurrRow,15) & "; "
end if
if instr(1,TheCCList,cells(CurrRow,16)) = 0 then ' diff CC
TheCCList = TheCCList & cells(CurrRow,16) & "; "
end if
else
if CurrRow <> 1 then
' do your output here because the company has changed
' probably call a subroutine because you will need it at the end too
end if
TheToList = ""
TheCCList = ""
end if
CurrRow = CurrRow + 1
Loop
' call your output subroutine one more time
I will address the problem of creating unique emailTO and emailCC from Cws sheet.
For this i suggest you use dictionaries.
Add a reference to 'Microsoft Scripting Runtime' as per screenshot.
Also given an improvement and suggestion on how to attach the file.
Sub Send_Row_Or_Rows_2()
Dim OutApp As Object
Dim OutMail As Object
Dim rng As Range
Dim Ash As Worksheet
Dim Cws As Worksheet
Dim Rcount As Long
Dim Rnum As Long
Dim FilterRange As Range
Dim FieldNum As Integer
Dim StrBody As String
Dim StrBody2 As String
Dim FileToAttach As String
Dim RngTo As Range
Set RngTo = Ash.Columns("H").Offset(1, 0).SpecialCells(xlCellTypeVisible)
StrBody = "<BODY style=font-size:11pt;font-family:Calibri>Dear Approver,<p>Please be informed that below invoices are waiting for your approval in BasWare for more than 10 days. Please check them and take action accordingly as soon as possible.</BODY>"
'On Error GoTo cleanup
Set OutApp = CreateObject("Outlook.Application")
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Set filter sheet, you can also use Sheets("MySheet")
Set Ash = Worksheets("rawdata")
'Set filter range and filter column (column with e-mail addresses)
Set FilterRange = Ash.Range("A4:M" & Ash.Rows.Count)
FieldNum = 4 'Filter column = D because the filter range start in column A
'Add a worksheet for the unique list and copy the unique list in A1
Set Cws = Worksheets.Add
FilterRange.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Cws.Range("A1"), _
CriteriaRange:="", Unique:=True
'Count of the unique values + the header cell
Rcount = Application.WorksheetFunction.CountA(Cws.Columns(1))
'If there are unique values start the loop
If Rcount >= 2 Then
'find unique emails for TO as CC
Dim dictTO As New Dictionary
Dim dictCC As New Dictionary
Dim emailTO As String
Dim emailCC As String
For Rnum = 2 To Rcount
emailTO = Trim(UCase(Cws.Range("O" & Rnum).Value))
emailCC = Trim(UCase(Cws.Range("P" & Rnum).Value))
If Not (emailTO = "") Then
If Not dictTO.Exists(emailTO) Then
Call dictTO.Add(emailTO, emailTO)
End If
End If
If Not (emailCC = "") Then
If Not dictCC.Exists(emailCC) Then
Call dictCC.Add(emailCC, emailCC)
End If
End If
Next Rnum
'remove CC emails that are in To dict
For Rnum = 1 To dictTO.Count
If dictCC.Exists(dictTO.Item(Rnum)) Then
dictCC.Remove (dictTO.Item(Rnum))
End If
Next
emailTO = ""
emailCC = ""
'Generate To Addresses
For Rnum = 1 To dictTO.Count
emailTO = emailTO & dictTO.Item(Rnum) & ","
Next
'Generate CC Addresses
For Rnum = 1 To dictTO.Count
emailCC = emailCC & dictCC.Item(Rnum) & ","
Next
With Ash.AutoFilter.Range
On Error Resume Next
Set rng = .SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
Set OutMail = OutApp.CreateItem(0)
FileToAttach = "C:\Users\gyousz\Desktop\Weekly_Customer CL3 and PT Control file_May_2018"
'fixed file being attached everytime - maybe saved a copy of Cws sheet and attach the workbook
On Error Resume Next
Dim fso As New FileSystemObject
With OutMail
.To = emailTO
.SentOnBehalfOfName = "CDM_Basware_Administration#esab.com"
.CC = emailCC
.Subject = "Reminder - Pending Invoices - More than 10 days"
.HTMLBody = StrBody & RangetoHTML(rng) & Signature
If (fso.FileExists(File)) Then 'checking if file exists
.Attachments.Add FileToAttach 'corrected how to add an attachment
End If
.Display
End With
On Error GoTo 0
Set OutMail = Nothing
'Close AutoFilter
Ash.AutoFilterMode = False
End If
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
Function RangetoHTML(rng As Range)
Dim fso As Object
Dim ts As Object
Dim TempFile As String
Dim TempWB As Workbook
TempFile = Environ$("temp") & "/" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
'Copy the range and create a new workbook to paste the data in
rng.Copy
Set TempWB = Workbooks.Add(1)
With TempWB.Sheets(1)
.Cells(1).PasteSpecial Paste:=8
.Cells(1).PasteSpecial xlPasteValues, , False, False
.Cells(1).PasteSpecial xlPasteFormats, , False, False
.Cells(1).Select
Application.CutCopyMode = False
On Error Resume Next
.DrawingObjects.Visible = True
.DrawingObjects.Delete
On Error GoTo 0
End With
'Publish the sheet to a htm file
With TempWB.PublishObjects.Add( _
SourceType:=xlSourceRange, _
Filename:=TempFile, _
Sheet:=TempWB.Sheets(1).Name, _
Source:=TempWB.Sheets(1).UsedRange.Address, _
HtmlType:=xlHtmlStatic)
.Publish (True)
End With
'Read all data from the htm file into RangetoHTML
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(TempFile).OpenAsTextStream(1, -2)
RangetoHTML = ts.ReadAll
ts.Close
RangetoHTML = Replace(RangetoHTML, "align=center x:publishsource=", _
"align=left x:publishsource=")
'Close TempWB
TempWB.Close savechanges:=False
'Delete the htm file we used in this function
Kill TempFile
Set ts = Nothing
Set fso = Nothing
Set TempWB = Nothing
End Function
Good luck
try manipulating this;
Sub sendmail10101()
Dim obApp As Object
Dim NewMail As MailItem
Set obApp = Outlook.Application
Set NewMail = obApp.CreateItem(olMailItem)
'You can change the concrete info as per your needs
With NewMail
.Subject = Cells(21, 3).Value
.To = Cells(18, 3).Value
.Body = "Good day" & vbCrLf & "i hope you are keeping well " & vbCrLf & vbCrLf & "please can you assist with the below members infomation;" & vbCrLf & vbCrLf & vbCrLf & Cells(20, 3).Value
'.Attachments.Add ("C:\Attachments\Test File.docx") IF YOU WANT TO ADD AN ATTACHMENT
.Importance = olImportanceHigh
.Display 'YOU CAN CHANGE TO SEND WHEN READY TO AUTOMATE
End With
Set obApp = Nothing
Set NewMail = Nothing
End Sub
instead of duplicating run the for loop;
for i = 1 to 20 at start of code
cells(i,1) where the data to be looped
next i before end sub
and you can use a filer add on at the beginning of the code to filter before starting the loop (obviously make sure that you set a filter on the data before using this type of code);
Sub AutoFilter_Text_Examples()
'Examples for filtering columns with TEXT
Dim lo As ListObject
Dim iCol As Long
'Set reference to the first Table on the sheet
Set lo = Sheet1.ListObjects(1)
'Set filter field
iCol = lo.ListColumns("Product").Index
'Clear Filters
lo.AutoFilter.ShowAllData
'All lines starting with .AutoFilter are a continuation
'of the with statement.
With lo.Range
'Single Item
.AutoFilter Field:=iCol, Criteria1:="Product 2"
'2 Criteria using Operator:=xlOr
.AutoFilter Field:=iCol, _
Criteria1:="Product 3", _
Operator:=xlOr, _
Criteria2:="Product 4"
'More than 2 Criteria (list of items in an Array function)
.AutoFilter Field:=iCol, _
Criteria1:=Array("Product 4", "Product 5", "Product 7"), _
Operator:=xlFilterValues
'Begins With - use asterisk as wildcard character at end of string
.AutoFilter Field:=iCol, Criteria1:="Product*"
'Ends With - use asterisk as wildcard character at beginning
'of string
.AutoFilter Field:=iCol, Criteria1:="*2"
'Contains - wrap search text in asterisks
.AutoFilter Field:=iCol, Criteria1:="*uct*"
'Does not contain text
'Start with Not operator <> and wrap search text in asterisks
.AutoFilter Field:=iCol, Criteria1:="<>*8*"
'Contains a wildcard character * or ?
'Use a tilde ~ before the character to search for values with
'wildcards
.AutoFilter Field:=iCol, Criteria1:="Product 1~*"
End With
End Sub
and to clear filter;
Sub Clear_All_Table_Filters_On_Sheet()
Dim lo As ListObject
'Loop through all Tables on the sheet
For Each lo In Sheet1.ListObjects
'Clear All Filters for entire Table
lo.AutoFilter.ShowAllData
Next lo
End Sub
so you can use a message box which sets the filter and then triggers the automated mail depending on what you require and the filter gets undone and resets for next use.
Is it possible that it will filter 2 columns? Like it will filter country and date (which are less than today's date) or if possible it will filter only the highlighted cell on the second filter. Thank you. Btw, I'm using Ron de Bruin codes, copyrights to him.
Example of what I'm looking:sample excel
Sub Send_Row_Or_Rows_2()
Dim OutApp As Object
Dim OutMail As Object
Dim rng As Range
Dim Ash As Worksheet
Dim Cws As Worksheet
Dim Rcount As Long
Dim Rnum As Long
Dim FilterRange As Range
Dim FieldNum As Integer
On Error GoTo cleanup
Set OutApp = CreateObject("Outlook.Application")
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'Set filter sheet, you can also use Sheets("MySheet")
Set Ash = ActiveSheet
'Set filter range and filter column (column with e-mail addresses)
Set FilterRange = Ash.Range("A1:H" & Ash.Rows.Count)
FieldNum = 3 'Filter column = B because the filter range start in column A
'Add a worksheet for the unique list and copy the unique list in A1
Set Cws = Worksheets.Add
FilterRange.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Cws.Range("A1"), _
CriteriaRange:="", Unique:=True
'Count of the unique values + the header cell
Rcount = Application.WorksheetFunction.CountA(Cws.Columns(1))
'If there are unique values start the loop
If Rcount >= 2 Then
For Rnum = 2 To Rcount
'Filter the FilterRange on the FieldNum column
FilterRange.AutoFilter Field:=FieldNum, _
Criteria1:=Cws.Cells(Rnum, 1).Value
'If the unique value is a mail addres create a mail
'If Cws.Cells(Rnum, 1).Value Like "?*#?*.?*" Then
With Ash.AutoFilter.Range
On Error Resume Next
Set rng = .SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = Cws.Cells(Rnum, 1).Offest(0, 1).Value
.Subject = "Test mail"
.HTMLBody = RangetoHTML(rng)
.Display 'Or use Send
End With
On Error GoTo 0
Set OutMail = Nothing
'Close AutoFilter
Ash.AutoFilterMode = False
Next Rnum
End If
When you filter once using
FilterRange.AutoFilter Field:=FieldNum, _
Criteria1:=Cws.Cells(Rnum, 1).Value
all you have to do to filter it further is write another analogous statement after that:
FilterRange.AutoFilter Field:=FieldNum2, _
Criteria1:= 'your criteria
If you do so, it will be filtered on both chosen columns. Same goes for three, four, or multiple other filters on the same dataset.
Hi I got a code which would filter the unique values in the A column and copy the whole range from A1:H, but I want to ignore the first column and want range to be copied form B1:H.
Eg: if there is a table with marks of students and I want to post the individual marks table to every student separately. This macro is sending the table along with the student name which is in the first column, but I need only marks table, don't need students name along with that.
Here is my code
Sub Send_Row_Or_Rows_1()
Dim OutApp As Object
Dim OutMail As Object
Dim rng As Range
Dim Ash As Worksheet
Dim Cws As Worksheet
Dim Rcount As Long
Dim Rnum As Long
Dim FilterRange As Range
Dim FieldNum As Integer
Dim mailAddress As String
Dim StrBody As String
On Error GoTo cleanup
Set OutApp = CreateObject("Outlook.Application")
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
Set Ash = ActiveSheet
'Set filter range and filter column (Column with names)
Set FilterRange = Ash.Range("A1:H" & Ash.Rows.Count)
FieldNum = 1 'Filter column = A because the filter range start in A
'Add a worksheet for the unique list and copy the unique list in A1
Set Cws = Worksheets.Add
FilterRange.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=Cws.Range("A1"), _
CriteriaRange:="", Unique:=True
'Count of the unique values + the header cell
Rcount = Application.WorksheetFunction.CountA(Cws.Columns(1))
'If there are unique values start the loop
If Rcount >= 2 Then
For Rnum = 2 To Rcount
'Filter the FilterRange on the FieldNum column
FilterRange.AutoFilter Field:=FieldNum, _
Criteria1:=Cws.Cells(Rnum, 1).Value
'Look for the mail address in the MailInfo worksheet
mailAddress = ""
On Error Resume Next
mailAddress = Application.WorksheetFunction. _
VLookup(Cws.Cells(Rnum, 1).Value, _
Worksheets("Mailinfo").Range("A1:B" & _
Worksheets("Mailinfo").Rows.Count), 2, False)
On Error GoTo 0
If mailAddress <> "" Then
With Ash.AutoFilter.Range
On Error Resume Next
Set rng = .SpecialCells(xlCellTypeVisible)
On Error GoTo 0
End With
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.to = mailAddress
.Subject = "Test mail"
.HTMLBody = StrBody & RangetoHTML(rng)
.Display 'Or use Send
StrBody = Sheets("Body").Range("A1").Value & "<br>" & _
Sheets("Body").Range("A2").Value & "<br>" & _
Sheets("Body").Range("A3").Value & "<br><br><br>"
End With
On Error GoTo 0
Set OutMail = Nothing
End If
'Close AutoFilter
Ash.AutoFilterMode = False
Next Rnum
End If
cleanup:
Set OutApp = Nothing
Application.DisplayAlerts = False
Cws.Delete
Application.DisplayAlerts = True
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub
If you want to stick with your solution rather than using Word's Mailing tools,
Just change this line :
Set rng = .SpecialCells(xlCellTypeVisible)
To
Set rng = Application.Intersect(.SpecialCells(xlCellTypeVisible),Ash.Range("B:H"))
By using offset you can select specific filtered column without heading or with heading
Please have a look below code :
Set rng = .AutoFilter.Range.Offset(1, ColumnNumber).Resize(.AutoFilter.Range.Rows.Count - 1, ColumnCount).SpecialCells(xlCellTypeVisible)
ColumnNumber - start column to copy
ColumnCount - number of columns to copy
Try below one :
set rng = Ash.Autofilter.Range.Offset(1).Resize(Ash.AutoFilter.Range.Rows.Count - 1,7).SpecialCells(xlCellTypeVisible)