I have piece of VBA code in an excel workbook that is currently working, but when attempting to add another capability to the VBA, the code returns an error.
Background: On a button click, if Column U contains a value of "Y" an email will be sent with the name of the client (Column W) in the email subject & body. I would like to add in the email address of the account manager (Column V) so that it changes for each email sent.
I understand that Rng is on a procedure-level scope and that it's only visible to notify() so am aiming to define it beforehand and then "calling" it to mymacro(). I've tried to add an additional Sub, but that didn't work.
Have tried using 2 if statements (as below) but don't think this is on the right track either.
I think the issue might be with me incorrectly defining scopes? Or perhaps incorrectly defining "EmailAddr"?
Sub notify()
Dim Rng As Range
Dim EmailAddr As String
For Each Rng In Range("U3:W200")
If (Rng.Value = "Y") Then
mymacro theValue:=Rng.Offset(0, 2).Value
End If
If (Rng.Value = "Y") Then
mymacro EmailAddr = Rng.Offset(0, 1).Value
End If
Next Rng
End Sub
Private Sub mymacro(theValue As String, EmailAddr As String)
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
xMailBody = "Hi All" & vbNewLine & vbNewLine & _
"This alert has been created via the Compliance Register." & vbNewLine & vbNewLine & _
"Please review the insurance(s) for " & theValue & " that are due to expire in the next 30 days." & vbNewLine & vbNewLine
With xOutMail
.To = "generalmanager#gmail.com;financemanager#gmail.com"
.CC = EmailAddr
.BCC = ""
.Subject = "Insurance(s) for " & theValue & " are expiring soon."
.Body = xMailBody
.Display 'or use .Send
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
End Sub
The code works perfectly if all "EmailAddr" components are removed from both notify() and mymacro() - but, of course, leaves the CC component empty. I'm still a novice at intuitively changing code to suit my needs, so a point in the right direction would be great.
Let me know if I can clarify anything in the meantime.
You only need one loop; change
If (Rng.Value = "Y") Then
mymacro theValue:=Rng.Offset(0, 2).Value
End If
If (Rng.Value = "Y") Then
mymacro EmailAddr = Rng.Offset(0, 1)
End If
to
If (Rng.Value = "Y") Then
mymacro theValue:=Rng.Offset(0, 2).Value, EmailAddr:=Rng.Offset(0, 1)
End If
and remove the Dim EmailAddr As String, since it is a named argument of mymacro, not a local variable.
Related
I am new to macros and I cant seem to find an answer to my problem. I have created a couple macros which work perfectly except for one issue. When I first open my excel sheet I have to step into my macro and "reset" it then it will work fine until I save, close and reopen the sheet the next time.
When I step into it the first line of it is highlighted yellow with an arrow to the left of it indicating an error but I'm not sure what the error is or how to pull up the error msg.
The line highlighted yellow is "Sub DueDateReminder()"
Private Sub Worksheet_calculate()
Call DueDateReminder
End Sub
Sub DueDateReminder()
Dim c As Range
For Each c In Range("K19:K500")
If c.Value = "No" Then
c.Value = "Yes"
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
xMailBody = "Hello " & c.Offset(0, 4) & vbNewLine & vbNewLine & _
"This in automatic message to inform you that you have an upcoming due date regarding " & c.Offset(0, -3).Text & " on " & c.Offset(0, -1) & vbNewLine & vbNewLine & _
"Best Regards!"
On Error Resume Next
With xOutMail
.To = c.Offset(0, 3)
.CC = ""
.BCC = ""
.Subject = "Project Review Meeting Due Date"
.Body = xMailBody
.Attachments.Add
.display ' use .send for automatic email
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
End If
Next c
End Sub
UPDATE
I have reworked my code to look like the following:
Private Sub Worksheet_Change(ByVal Target As Range)
Call notify
End Sub
Sub notify()
Dim rng As Range
For Each rng In Range("N2:N502")
If (rng.Value = "Expired") Then
Call mymacro(rng.Address)
End If
Next rng
End Sub
Private Sub mymacro(theValue As String)
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
Dim myAddress As String
Dim myChemical As String
myAddress = Application.WorksheetFunction.Index(Range("A2:Q502"), Application.WorksheetFunction.Match("Expired", Range("N2:N502"), 0), 15)
myChemical = Application.WorksheetFunction.Index(Range("A2:Q502"), Application.WorksheetFunction.Match("Expired", Range("N2:N502"), 0), 3)
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
xMailBody = "Hello" & vbNewLine & vbNewLine & _
"This email is to let you know that you have an expired reference standard" & vbNewLine & _
"The Reference Standard that has expired is: " & myChemical & vbNewLine & vbNewLine & _
"Thank you," & vbNewLine & _
"Spreadsheet Manager"
On Error Resume Next
With xOutMail
.To = myAddress
.CC = ""
.BCC = ""
.Subject = "Expired Reference Standard"
.Body = xMailBody
.Display 'or use .Send
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
End Sub
This new code now populates the emails with the correct fields, but it is getting stuck on the first entry that equals "Expired" in column N. It knows that there are multiple instances of "Expired" because when I change myChemical in the message to theValue multiple emails populate with different cell values. Is there a way to fix the code to avoid this error as is, or would I have to add a For-Next statement into the code?
That's an ambitious project. You don't need the code to be continuously running if you have it triggered from a Worksheet_Change event. You can use that event to "watch" just the contents of specific rows, columns or ranges, and run the code when a cell in the watched range is changed by the user. (https://learn.microsoft.com/en-us/office/vba/api/excel.worksheet.change)
The Worksheet_Change event runs in the Sheet module and uses the concept of the "target" range, which is the range or cell that triggered the macro to run. If it is a single cell, you now have the address of that cell and from there you can deduce the row number and get other data in that row, or data related to that cell dependent on your sheet layout.
You can now to construct the body text with the details you need.
I'm trying to put together a piece of code but it keeps returning an error...have put together some very simple code in the past but am not so well versed in fixing code, and can't seem to troubleshoot what's going wrong in the code I have so far...
Background:
I have an excel workbook for compliance (macro-enabled of course) that has various columns with due dates. If any of the columns have due dates within the next 30 days, column V will return "Y". At the start of the month, if any of the rows have "Y" listed in column V, an email alert will be sent to the specified email addresses with the name of the client (in column W) in both the subject and body.
Started off with identifying the cell reference of every "Y" occurrence (as "theValue"). The code seems to work without entering any values for the client name in the subject & body.
Have tried to use the offset value option but it keeps returning an error - am not sure how to rectify.
Any input is appreciated as to what I'm doing wrong - let me know if I can clarify on anything further in the meantime!
Sub notify()
Dim Rng As Range
For Each Rng In Range("V3:W200")
If (Rng.Value = "Y") Then
Call mymacro(Rng.Value)
End If
Next Rng
End Sub
Private Sub mymacro(theValue As String)
Dim xOutApp As Object
Dim xOutMail As Object
Dim xMailBody As String
Set xOutApp = CreateObject("Outlook.Application")
Set xOutMail = xOutApp.CreateItem(0)
xMailBody = "Hi All" & vbNewLine & vbNewLine & _
"This alert has been automatically created by the Client Compliance Register. Please ensure information for " & Rng.Offset(0, 1).Value & " is up to date." & vbNewLine & vbNewLine
With xOutMail
.To = "generalmanager#gmail.com"
.CC = "accountmanager#gmail.com"
.BCC = "managingdirector#gmail.com"
.Subject = "Detail(s) for " & Rng.Offset(0, 1).Value & " are expiring soon."
.Body = xMailBody
.Display 'use .Send once complete
End With
On Error GoTo 0
Set xOutMail = Nothing
Set xOutApp = Nothing
End Sub
As alluded to in the comments:
rng only has procedure-level scope. The variable rng is only visible to the procedure notify. You can't use it inside mymacro.
To accomplish your task, pass rng.Offset(0, 1).Value as an argument when calling mymacro.
Call mymacro(Rng.Offset(0, 1).Value)
The Call here is redundant; all you need is
mymacro Rng.Offset(0, 1).Value '<< no parentheses!
or
mymacro theValue:=Rng.Offset(0, 1).Value
Then within mymacro, change each instance of
Rng.Offset(0, 1).Value
to
theValue
because theValue is in scope for mymacro; it's the argument you're passing.
For further reading, see understanding scope and visibility.
As mentioned in the subject of this post, I am attempting to send emails automatically by running a macro so that if cell J2 has the words "Send Reminder" in it, then the email address in cell K2 should be sent an email with the subject title in cell L2 and Body in Cell M. I have a list of emails ranging from cells K2:K59
Currently I have the following code:
Sub SendEm()
Dim i As Integer, Mail_Object, Email_Subject, o As Variant, lr As Long
lr = Cells(Rows.Count, "K").End(xlUp).Row
Set Mail_Object = CreateObject("Outlook.Application")
For i = 2 To lr
With Mail_Object.CreateItem(o)
.Subject = Range("L2").Value
.To = Range("K" & i).Value
.Body = Range("M2").Value
.Send
End With
Next i
MsgBox "E-mail successfully sent", 64
Application.DisplayAlerts = False
Set Mail_Object = Nothing
End Sub
I already have outlook open with references for Microsoft Outlook 14.0 Object Library selected amongst others, and I get an error saying " Run-time error '287' Application-definer or object-defined error, if i try to debug it, it highlights .Send in my code.
Can anyone help point out what I am doing wrong? I have tried various types of code to send emails based on different youtube videos etc. but seem to run into this error each time!
Thanks for your help ahead of time!
Edit1: I updated the code to the following based on suggestions and now a different issue:
Private Sub CommandButton21_Click()
'~~> Excel Objects/Variables
Dim ws As Worksheet
Dim lRow As Long, i As Long
'~~> Outlook Objects/Variables
Dim OutApp As Object
Dim OutMail As Object
Dim emailRange As Range, cl As Range
Dim sTo As String
Dim subjectRange As Range, c2 As Range
Dim sSubject As String
Dim bodyRange As Range, c3 As Range
Dim sBody As String
'~~> Set your worksheet here
Set ws = ThisWorkbook.Sheets("Sheet11")
'~~> Open Outlook
Set OutApp = CreateObject("Outlook.Application")
With ws
'~~> Get last row from Col J as that is what we
'~~> are going to check for the condition
lRow = .Range("J" & .Rows.Count).End(xlUp).Row
'~~> Loop through the rows
For i = 2 To lRow
If .Range("J" & i).Value = "Send Reminder" Then
'~~> Create new email
Set emailRange = Worksheets("Sheet11").Range("K2:K59")
For Each cl In emailRange
sTo = sTo & ";" & cl.Value
Next
sTo = Mid(sTo, 2)
Set subjectRange = Worksheets("Sheet11").Range("L2:L59")
For Each c2 In subjectRange
sSubject = sSubject & ";" & c2.Value
Next
sSubject = Mid(sSubject, 2)
Set bodyRange = Worksheets("Sheet11").Range("M2:M59")
For Each c3 In bodyRange
sBody = sBody & ":" & c3.Value
Next
sBody = Mid(sBody, 2)
Set OutMail = OutApp.CreateItem(0)
'On Error Resume Next
With OutMail
'~~> Customize your email
.To = ""
.CC = sTo
.BCC = ""
.Subject = "typed subject1" & sSubject
.Body = ""
.Display '<~~ Change to .Send to actually send it
End With
End If
Next i
End With
End Sub
This code opens up multiple windows in outlook with all the emails listed in K2:K59. For example, if three cells in J2:J59 have send reminder, i open 3 email windows with all the emails listed in the cc box, instead of either multiple windows with individual emails or one window with all the emails. I think I have to close the loop somehow but am not certain how! Thanks for your help.
Mail_Object.CreateItem(o)
Shouldn't that be
Mail_Object.CreateItem(0)
0 and not o
In the below code, you are not required to set a reference to MS Outlook Object Library. I am using Late Binding with MS Outlook.
Try this (Untested)
I have commented the code so you shall not have a problem understanding the code but if you do then simply post back :)
Option Explicit
Sub Sample()
'~~> Excel Objects/Variables
Dim ws As Worksheet
Dim lRow As Long, i As Long
'~~> Outlook Objects/Variables
Dim OutApp As Object
Dim OutMail As Object
'~~> Set your worksheet here
Set ws = ThisWorkbook.Sheets("Sheet1")
'~~> Open Outlook
Set OutApp = CreateObject("Outlook.Application")
With ws
'~~> Get last row from Col J as that is what we
'~~> are going to check for the condition
lRow = .Range("J" & .Rows.Count).End(xlUp).Row
'~~> Loop through the rows
For i = 2 To lRow
If .Range("J" & i).Value = "Send Reminder" Then
'~~> Create new email
Set OutMail = OutApp.CreateItem(0)
With OutMail
'~~> Customize your email
.To = ws.Range("K" & i).Value
.Subject = ws.Range("L" & i).Value
.Body = ws.Range("M" & i).Value
.Display '<~~ Change to .Send to actually send it
End With
End If
Next i
End With
End Sub
Since you have Outlook open you do not have to do anything complicated.
Set Mail_Object = GetObject(, "Outlook.Application")
I did something similar yesterday, here is the code I used, hope it helps you out.
Sub EmailCopy()
Dim oApp, oMail As Object, X As Long, MyBody As String
Application.ScreenUpdating = False
On Error Resume Next
Set oApp = CreateObject("Outlook.Application")
For X = 2 To Range("A" & Rows.Count).End(xlUp).Row
MyBody = Replace(Join(Application.Transpose(Range("E5:E" & Range("D" & Rows.Count).End(xlUp).Row - 1).Value), vbLf & vbLf), "<FirstName>", Range("B" & X).Text)
MyBody = MyBody & vbLf & vbLf & Join(Application.Transpose(Range("E" & Range("D" & Rows.Count).End(xlUp).Row & ":E" & Range("E" & Rows.Count).End(xlUp).Row)), vbLf)
Set oMail = oApp.CreateItem(0)
With oMail
.To = Range("A" & X).Text
.cc = Range("E1").Text
.Subject = Range("E2").Text
.Body = MyBody
.Attachments.Add Range("E3").Text
.Display
If UCase(Range("E4").Text) = "SEND" Then
.Send
ElseIf UCase(Range("E4").Text) = "DRAFT" Then
.Save
.Close False
Else
MsgBox "You need to choose Draft or Send in cell E4"
End
End If
End With
Application.ScreenUpdating = True
Set oMail = Nothing
Next
Set oApp = Nothing
End Sub
Recipients go in Column A and First Name goes in column B, Any CC's go in E1, Subject goes in E2, Any attachment links go in E3, E4 is either Draft or Send to create a draft or do a send.
Then the message body goes in E5 down as far as you want, each line will be separated by a double return. Anywhere you use FirstName wrapped in greater than and less than signs the code will replace it with the person's First Name from column B.
Straight after that put the signature you want and put "Signature" in column D next to the start of it, this will be separated by single returns.
I'm trying to build a macro that grabs a selection of cells from an Excel spreadsheet, pastes the cells into a new outlook email, then changes the format of the cells.
Specifically I want to convert the table to text, then change the font to Arial size 10.
The code below does the above, but I haven't been able to figure out how to convert the table to text, then change the text font.
Can anyone help?
Sub Email_test()
Dim rng As Range
Dim OutApp As Object
Dim OutMail As Object
Set rng = Nothing
Set rng = Sheets("Master").Range("A1:B99").SpecialCells(xlCellTypeVisible)
If rng Is Nothing Then
MsgBox "The selection is not a range or the sheet is protected. " & _
vbNewLine & "Please correct and try again.", vbOKOnly
Exit Sub
End If
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = "User#company.com"
.CC = ""
.BCC = ""
.Subject = "Cells as text "
.HTMLbody = RangetoHTML(rng)
' In place of the following statement, you can use ".Display" to
' display the e-mail message.
.Display
End With
End Sub
This will work for you, instead of HTMLbody use body also removed your range to html function
Sub Email_test()
Dim rng As Range
Dim OutApp As Object
Dim OutMail As Object
Set rng = Nothing
Set rng = Sheets("Master").Range("A1:B99").SpecialCells(xlCellTypeVisible)
If rng Is Nothing Then
MsgBox "The selection is not a range or the sheet is protected. " & _
vbNewLine & "Please correct and try again.", vbOKOnly
Exit Sub
End If
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
Dim v As Variant: v = rng.Value
Dim tempStr As String: tempStr = ""
For i = LBound(v, 1) To UBound(v, 1)
For j = LBound(v, 2) To UBound(v, 2)
If j = 2 Then
tempStr = tempStr & v(i, j) & vbCrLf
Else
tempStr = tempStr & v(i, j) & " "
End If
Next j
Next i
Set OutApp = CreateObject("Outlook.Application")
Set OutMail = OutApp.CreateItem(0)
With OutMail
.To = "User#company.com"
.CC = ""
.BCC = ""
.Subject = "Cells as text "
.body = tempStr
' In place of the following statement, you can use ".Display" to
' display the e-mail message.
.Display
End With
End Sub
Please mark as an answer if you are satisfied with reply
The Outlook object model provides three main ways for working item bodies:
Body - a string representing the clear-text body of the Outlook item.
HTMLBody - a string representing the HTML body of the specified item.
Word editor - the Microsoft Word Document Object Model of the message being displayed. The WordEditor property of the Inspector class returns an instance of the Document class from the Word object model which you can use to set up the message body.
You can read more about all these ways in the Chapter 17: Working with Item Bodies. It us up to you which way is to choose.