If I change c.Offset(, 1) to c.Offset(, 0) an email will get sent to the first recipient but not the next. If I change c.Offset(, 0) to c.Offset(, 1) I get outlook does not recognize one or more names. How can I get the syntax correct to send the email to multiple users? The design of the spreadsheet is below as well as the VB. I apologize for the lengthy message, just trying to be complete. Thank you :).
Design of spreadsheet
A B C D
Email Date Comment 1 Comment 2
123#gmail.com
456#hotmail.com
when the spreadsheet opens the below runs automatically:
VB
Private Sub Workbook_Open()
Dim sR As String
Dim sFile As String
Sheets("Email").Activate
Range("A1").Select
If MsgBox("Are there any issues to report", vbYesNoCancel) = vbYes Then
Range("D2").Value = "x"
MsgBox ("Please select an issue and save"), vbExclamation
Else
Range("C2").Value = "x"
If vbCancel Then Application.SendKeys "%{F11}", True
'define path
MyFileCopy = "L:\NGS\HLA LAB\total quality management\QC & QA\DOSE reports\DOSE reporting form Attachment.xlsx"
'create connection, check condition, send email
Set OutApp = CreateObject("Outlook.Application")
Set WS = ThisWorkbook.Sheets("Email")
With WS
Set Rng = .Range("A2", .Range("A" & .Rows.Count).End(xlUp))
End With
For Each c In Rng
Msg = "For " & WS.Cells(2, 2) & Chr(14) & Chr(14)
For i = 3 To 4
If LCase(WS.Cells(c.Row, i)) = "x" Then
Msg = Msg & " -" & WS.Cells(1, i) & Chr(14)
End If
Next
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = c.Offset(, 1)
.CC = ""
.BCC = ""
.Subject = "Daily Operational Safety Briefing"
.Body = Msg
If Range("D2").Value & Chr(14) = "x" Then .Attachments.Add MyFileCopy, 1
.Send
End With
Next c
'confirm message sent, clear sheet, and delete copy
MsgBox "The data has been emailed sucessfully.", vbInformation
Range("C2:D2").ClearContents
Kill MyFileCopy
Set OutMail = Nothing
Set OutApp = Nothing
'Exit and do not save
Application.Quit
ThisWorkbook.Close SaveChanges:=False
End If
End Sub
All you need is .To = c because your sent is sent to column A, which has the addresses.
There is no need to offset the c cell in the range at all.
If you wish to send an email to more than one address, semi-colons need to be between each address, as this is how Outlook resolves that there is more than one address.
So, based on your example above:
.To = c & ";" & c.Offset(1) ' & ";" c.Offset(2) to carry it further.
Note that I also Offset c by 1 Row. You wrote c.Offset(,1) meaning it will offset 1 column. The arguments for Offset are Offset(rows,columns,[row height],[column width])
Related
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
I have three columns: A) Enterprises B) Email address matching the enterprise C) Yes or No
If there is a YES in column C, I want to send a message to the email address in column B.
This is what I have. Nothing is happening.
Sub Test2()
Dim OutApp As Outlook.Application
Dim OutMail As Outlook.MailItem
Dim cell As Range
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
On Error GoTo cleanup
For Each cell In Columns("B").Cells.SpecialCells(xlCellTypeConstants)
If cell.Value Like "?*#?*.?*" And _
LCase(Cells(cell.Row, "C").Value) = "yes" _
And LCase(Cells(cell.Row, "D").Value) <> "send" Then
Set OutMail = OutApp.CreateItem(olMailItem)
On Error Resume Next
With OutMail
.To = cell.Value
.Subject = "Reminder"
.Body = "Dear " & Cells(cell.Row, "A").Value _
& vbNewLine & vbNewLine & _
"Please contact us to discuss bringing " & _
"your account up to date."
Attachments.Add ("\\C:\test.pdf")
.Send '
End With
On Error GoTo 0
Cells(cell.Row, "D").Value = "send"
Set OutMail = Nothing
End If
Next cell
cleanup:
Set OutApp = Nothing
Application.ScreenUpdating = True
End Sub
The code below will loop through Row 2 to the last row in the UsedRange and make sure that Columns A, B & C are not empty as well as check to make sure Column D is empty, which the code uses as a flag to show whether the email has previously been sent.
I've added a Regex validation function to the code to validate the email address.
Sub LoopThroughRange_SendEmail()
Dim OutApp As Object: Set OutApp = CreateObject("Outlook.Application")
Dim OutMail As Object: Set OutMail = OutApp.CreateItem(0)
Dim oRegEx As Object
Set oRegEx = CreateObject("VBScript.RegExp")
Dim i As Long
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
'declare and set the worksheet you are working with
For i = 2 To ws.UsedRange.Rows.Count
'loop from Row 2 To Last Row in UsedRange
If ws.Cells(i, "A").Value <> "" And ws.Cells(i, "B").Value <> "" And ws.Cells(i, "C").Value = "Yes" And ws.Cells(i, "D").Value = "" Then
' make sure that Columns A, B & C are not empty and D is empty (which we will use as a flag to show that the email did get sent.
If ValidEmail(ws.Cells(i, "B").Value, oRegEx) Then
With OutMail
.To = ws.Cells(i, "B").Value
.CC = ""
.BCC = ""
.Subject = "Reminder"
.Body = "Dear " & Cells(cell.Row, "A").Value _
& vbNewLine & vbNewLine & _
"Please contact us to discuss bringing " & _
"your account up to date."
.Attachments.Add ("\\C:\test.pdf")
.Display '.Send
End With
ws.Cells(i, "D").Value = "Sent # " & Format(Now(), "yyyy-MM-dd hh:mm:ss")
Else
ws.Cells(i, "D").Value = "Email not valid"
End If
End If
End Sub
Public Function ValidEmail(pAddress As String, ByRef oRegEx As Object) As Boolean
With oRegEx
.Pattern = "^(([a-zA-Z0-9_\-\.\']+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)(\s*;\s*|\s*$))+$" 'pattern for multiple email addresses included
ValidEmail = .test(pAddress)
End With
End Function
I continue my work starting from the 1st question here:
Excel VBA - Outlook Email - Body created with rows having a particular value
Now i have another problem.
I want to repeat the below MACROs on all the SHEETS of my file.
In particular, how can I repeat this function on different SHEETS by only clicking in 1 button present in all the sheets?
All the sheets have the same structure.
I mean, the table resulting in the email must be implemented by adding the datas in all the sheets.
The data should be copied starting from the 1st sheet, for ex. TEST(1) to the last sheet, TEST(9).
The email generated after this process must be ONLY one.
Determine the body range
Sub EmailRange()
Dim Initial As Range, Final As Range, nCell As Range
On Error Resume Next
Set Initial = Sheets("TEST").Application.InputBox("Range", xTitleId, "B1:K20", Type:=8)
On Error GoTo 0
For Each nCell In Initial.Resize(Initial.Rows.Count, 1)
If nCell.Offset(, -1) = "X" Then
If Not Final Is Nothing Then
Set Final = Union(Final, nCell.Resize(1, Initial.Columns.Count))
Else
Set Final = nCell.Resize(1, Initial.Columns.Count)
End If
End If
Next nCell
If Not Final Is Nothing Then
MAIL Final
Else
MsgBox "ATTENZIONE!!!" & vbNewLine & "Seleziona un range di celle valido.", vbOKOnly
End If
End Sub
Send the email with the range
Sub MAIL(Final as Range)
Dim OutApp As Object, OutMail As Object
Dim StrBodyIn As String, StrBodyEnd As String
StrBodyIn = "Bonjour," & "<br>" & " " & "<br>" & "Buongiorno," & "<br>"
StrBodyEnd = " " & "<br>" & "Cordialement" & "<br>" & " " & "<br>" & Range("M2") & "<br>"
Application.EnableEvents = False
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.To = "email#gmail.com"
.CC = ""
.BCC = ""
.Subject = "SITUATION"
.HTMLBody = "<p style='font-family:calibri;font-size:16'>" & StrBodyIn & RangetoHTML(Final) & "<p style='font-family:calibri;font-size:16'>" & StrBodyEnd
.Display 'or use .Send
End With
On Error GoTo 0
Set OutMail = Nothing
Set OutApp = Nothing
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
I've tried with something like this, but it does not work:
For I = 1 To Worksheets.Count
Sheets(I).Select
***[...]CODE OF "Determine the body range"***
Next I
Sheets("TEST(I)").Select
What is the correct syntax for pausing a VBA until the user saves an excel attachment? In the VB below the user is prompted upon opening the workbook with a selection, if that selection is yes then another message box appears asking them to fill out a form and save. I am trying to pause the VB until save is clicked. However, I am getting many compile errors currently. The lines with a ** ** were added to try and accomplish thisThank you :).
VB
Private Sub Workbook_Open()
Dim WS As Worksheet, Rng As Range, c As Range
Dim OutApp As Object, OutMail As Object
Dim Msg As String, Addr As String, FName As String, i As Long
Dim obj As Object
**Dim MyDoc As Document**
Dim MyFileCopy As String
Dim intAnswer As Integer
'define path
MyFileCopy = "L:\NGS\HLA LAB\total quality management\QC & QA\DOSE reports\DOSE reporting form Attachment.xlsx"
'open sheet
Sheets("Email").Activate
intAnswer = MsgBox("Are there any issues to report", vbYesNoCancel)
Select Case intAnswer
Case vbYes
Range("D2").Value = "x"
MsgBox ("Please select an issue and save"), vbExclamation
'create a separate sheet2 to mail out and pause VB
Sheets(2).Copy
Set wkb = ActiveWorkbook
With wkb
**Set MyDoc = Documents.Add
MyDoc.SaveAs "MyFileCopy.xlsx"
DoEvents
Do
Loop Until MyDoc.Saved
.Close True**
End With
Case vbCancel
Application.SendKeys "%{F11}", True
Case Else
Range("C2").Value = "x"
End Select
'create connection, check condition, send email
Set OutApp = CreateObject("Outlook.Application")
Set WS = ThisWorkbook.Sheets("Email")
With WS
Set Rng = .Range("A2", .Range("A" & .Rows.Count).End(xlUp))
End With
For Each c In Rng
Msg = "For " & WS.Cells(2, 2) & Chr(14) & Chr(14)
For i = 3 To 4
If LCase(WS.Cells(2, i)) = "x" Then
Msg = Msg & " -" & WS.Cells(1, i) & Chr(14)
End If
Next
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = c
.CC = ""
.BCC = ""
.Subject = "Daily Operational Safety Briefing"
.Body = Msg
If Range("D2").Value & Chr(14) = "x" Then .Attachments.Add MyFileCopy, 1
.Send
End With
Next c
Set OutMail = Nothing
Set OutApp = Nothing
'confirm message sent, clear sheet, and delete copy
MsgBox "The data has been emailed sucessfully.", vbInformation
Range("C2:D2").ClearContents
Kill MyFileCopy
'Exit and do not save
Application.Quit
ThisWorkbook.Close SaveChanges:=False
End Sub
When there are multiple users the first email sent is correct but in the second only the For 10/23/2015 shows up. Below is a copy of a correct email and where the data comes from and the code.
Correct Email
**For 10/2/2015** ( Msg = "For " & c.Offset(, 1) & Chr(14) & Chr(14)
**-There are no issues to report in the HLA & Molecular Diagnostics Laboratory.** ( For i = 3 To 4
If LCase(WS.Cells(c.Row, i)) = "x" Then
Msg = Msg & " -" & WS.Cells(1, i) & Chr(14)
End If
Next)
VBA
Private Sub Workbook_Open()
Dim sR As String
Dim intAnswer As Integer
'open sheet
Sheets("Email").Activate
intAnswer = MsgBox("Are there any issues to report", vbYesNoCancel)
Select Case intAnswer
Case vbYes
Range("D2").Value = "x"
MsgBox ("Please select an issue and save"), vbExclamation
Case vbCancel
Application.SendKeys "%{F11}", True
Case Else
Range("C2").Value = "x"
End Select
'define path
MyFileCopy = "L:\NGS\HLA LAB\total quality management\QC & QA\DOSE reports\DOSE reporting form Attachment.xlsx"
'create connection, check condition, send email
Set OutApp = CreateObject("Outlook.Application")
Set WS = ThisWorkbook.Sheets("Email")
With WS
Set Rng = .Range("A2", .Range("A" & .Rows.Count).End(xlUp))
End With
For Each c In Rng
Msg = "For " & WS.Cells(2, 2) & Chr(14) & Chr(14)
For i = 3 To 4
If LCase(WS.Cells(c.Row, i)) = "x" Then
Msg = Msg & " -" & WS.Cells(1, i) & Chr(14)
End If
Next
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = c
.CC = ""
.BCC = ""
.Subject = "Daily Operational Safety Briefing"
.Body = Msg
If Range("D2").Value & Chr(14) = "x" Then .Attachments.Add MyFileCopy, 1
.Send
End With
Next c
'confirm message sent, clear sheet, and delete copy
MsgBox "The data has been emailed sucessfully.", vbInformation
Range("C2:D2").ClearContents
Kill MyFileCopy
Set OutMail = Nothing
Set OutApp = Nothing
'Exit and do not save
Application.Quit
ThisWorkbook.Close SaveChanges:=False
End Sub
I know the answer to this based on your previous question, where you shared your data structure. (Otherwise, your post did not provide enough detail to be clear on what you are asking.)
The issue you have is that as you loop through each cell in the Column A (all the emails) via For each c in rng, you also test the conditions of if column C or D contains x against each row in If LCase(WS.Cells(c.Row, i)) = "x" Then. Since your data set only has the message information in row 2 (as shown in your previous question), you need to always check row 2 each time you loop.
All that said, change
If LCase(WS.Cells(c.Row, i)) = "x" Then
to
If LCase(WS.Cells(2, i)) = "x" Then
and you will get the results you desire.