Share via

Looping code problem sending emails in VBA

Anonymous
2013-12-26T17:35:45+00:00

I am having two issues with the code below.  Any help or advice would be appreciated.

Source is an Excel Pivot Table with multiple rows per employee.  Each row is unique by employee+project.  AN employee can be on multiple projects.  The objective is to send emails to employees listing which projects they are schedule to work on next week.

Issues:

1.  The high level loop "Do Until Cells(N,4)="" doesn't stop the entire code.  It keeps going and then finds no email address (at the bottom in the line "aEmail.Recipients.Add Cells(N, 6)") and then fails.

2.  This does run and create the email, but it creates wrongly one email per Project to which an employees is assigned (thereby sending one employee multiple emails).  It is supposed to send one email to each employee with all projects listed in the body.

Sub EMAIL_STAFFING()

Dim N As Integer

Dim Y As Integer

Dim Z As Integer

'Dim s1 As Worksheet

's1 = "Individual Sums by Week"

's1 = "Sheet1"

s1 = "Email"

N = 4

Do Until Cells(N, 4) = ""

    If Cells(N, 1) = "DONE" Then

        GoTo 100

    Else

        Y = N

        x = 1

        LNAME = Cells(Y, 4)

        FNAME = Cells(Y, 5)

        Z = Application.WorksheetFunction.Match(LNAME, Sheets(s1).Range(Cells(N, 4), Cells(1000, 4)), 0)

        BODY1 = ""

        Do Until Y > ((N + Z) - 1)

            BODY1 = BODY1 & vbLf & _

                "Project" & x & " : " & Cells(Y, 8) & ", " & Cells(Y, 9) & vbLf & _

                "Hours      : " & Cells(Y, 3) & vbLf

                Cells(Y, 1) = "DONE"

            x = x + 1

            Y = Y + 1

        Loop

'Dim aOutlook As Object

'If aOutlook Is Nothing Then

Set aOutlook = CreateObject("Outlook.application")

'Else

'Set aOutlook = GetObject(, "Outlook.Application")

'End If

'Set aOutlook = GetObject(, "Outlook.Application")

'If aOutlook Is Nothing Then Set aOutlook = New Outlook.Application

       ' outlook.Application

        '  Set aEmail = OutlookApp.CreateItem(olmailitem)

Set aEmail = aOutlook.CreateItem(olmailitem)

        'set Importance indicator on email message

        aEmail.Importance = olImportanceHigh

        'Set Subject of email message

        aEmail.Subject = "Your staffing for the upcoming week, " & Format(Cells(Y - 1, 2), "MMMM D, YYYY")

        ' aEmail.Subject = "Your staffing for this week-- " & Cells(y - 1, 4)

        'Set Line 1 of Body - Greeting

        aEmail.Body = "Hi, " & FNAME & ", " & vbLf & vbLf & _

                "This week (commencing " & Format(Cells(Y - 1, 2), "MMMM D, YYYY") & ")" & " you should be working on the following project(s):" & vbLf & BODY1 & _

         vbLf & "Ìf this is not your understanding based on your project PM`s instruction, please inform Patty Yuen asap."

        'Set email recipient (who is resource whose hours are captured in the email)

        aEmail.Recipients.Add Cells(N, 6)

        'Send the email

        'below is for sending email out immedaitely

        'aEmail.Send

        'below is to put emails all in Outlook draft folder to be manually sent from there

        aEmail.Save

    End If

100

N = N + 1

Loop

Range(Cells(1, 1), Cells(65536, 1)).ClearContents

End Sub

Microsoft 365 and Office | Excel | For home | Windows

Locked Question. This question was migrated from the Microsoft Support Community. You can vote on whether it's helpful, but you can't add comments or replies or follow the question.

0 comments No comments

15 answers

Sort by: Most helpful
  1. Anonymous
    2014-01-01T06:52:07+00:00

    If OssieMac's macro doesn't work for you, with a few small changes to the source worksheet (the header row needs to be on the top row and all the columns must be named), you could use http://www.gmayor.com/ManyToOne.htm which should be able to create the type of document you require and e-mail it either as the body of a message or as an attachment to a personalised message..

    Was this answer helpful?

    0 comments No comments
  2. OssieMac 48,001 Reputation points Volunteer Moderator
    2014-01-01T02:15:17+00:00

    I have posted the Excel 2013 workbook to SkyDrive:

    Hello Kim,

    Hope it all works for you. You should be able to copy the code below into your workbook and run it.

    What the code does:-

    • Firstly I have written the code so that you do not need to set the Reference in Tools -> References to Outlook. This is called late binding and the advantage is that it should work with any version of Outlook. (Using early binding can cause problems with compatibility of earlier versions of Outlook.)
    • Copies the data from the Pivot to a temporary worksheet so that AutoFilter can be applied. (Also copies some formatting but this part is not really necessary other than I used it during testing.)
    • Copies the email addresses to another temporary worksheet and removes the duplicates.
    • Loops through the unique list of email addresses and sets the filter to each email address in turn.
    • Copies the visible Projects for the email address to a variable.
    • Creates a variable for the email body from the required data and extracts the recipients name and the date from the first row to use in the body.
    • Uses the created strings to set the email parameters for Subject, Body etc.
    • Uses the value of the range variable that is used to loop through the unique list of email addresses as the recipient.
    • Saves the emails to the Drafts folder.
    • Deletes the temporary worksheets.

    Feel free to get back to me with an queries. I have tested and placed all emails in my Drafts folder.

    Code has been edited since initial posting with additional functionality as requested.

    Sub Email()

        'Code uses late binding.

        'DO NOT set the Reference to Outlook in the Tools -> References

        Dim objOutlook As Object       'Uses late binding

        Dim objMailItem As Object      'Uses late binding

        Const cstMailItem = 0          'Required when late binding used

        Const cstImportanceHigh = 2    'Required when late binding used

        Dim wsPivot As Worksheet

        Dim wsCopy As Worksheet

        Dim wsUnique As Worksheet

        Dim rngToFind As Range

        Dim rngUnique As Range

        Dim celUniq As Range

        Dim celProj As Range

        Dim rngFilt As Range

        Dim strBody As String

        Dim strProjs As String

        Dim strSubject As String

        Set wsPivot = Worksheets("Email3")   'Pivot output. (Edit "Email3" if not correct name)

        'Test if SendEmail column has at least one "Y" otherwise terminate the processing

        If WorksheetFunction.CountIf(wsPivot.Columns("G:G"), "Y") = 0 Then

            MsgBox "There are no ""Y"" values in SendEmail column." & vbCrLf & _

                    "Processing terminated."

            Set wsPivot = Nothing

            Exit Sub

        End If

        Set wsCopy = Sheets.Add(After:=Sheets(Sheets.Count))    'Add temporary worksheet for copy of data

        Set wsUnique = Sheets.Add(After:=Sheets(Sheets.Count))  'Add temporary worksheet for unique list of email addresses

        wsPivot.Cells.Copy    'Copy data from Pivot

        wsCopy.Cells(1, 1).PasteSpecial Paste:=xlPasteValues    'Paste values only to temporary worksheet.

        With wsCopy

            wsPivot.Rows("1:1").Copy     'Copy first row of Pivot for transferring format to temp copy

            .Cells(1, 1).PasteSpecial Paste:=xlPasteFormats 'Paste the formats so they look like dates in row 1

            wsPivot.Columns("B:B").Copy  'Copy column B of Pivot  for transferring format to temp copy

            .Cells(1, "B").PasteSpecial Paste:=xlPasteFormats   'Paste formats so they look like dates in column B

            'Add a Column headers to blank cell ready for AutoFilter

            .Cells(3, "B") = "Date"

            .Cells(3, "C") = "Col_C"

            'Turn on AutoFilter

            .Range(.Cells(3, "B"), .Cells(3, .Columns.Count).End(xlToLeft)).AutoFilter

            .Columns.AutoFit

        End With

        With wsCopy

            'Set filter to display only records with "Y" in SendEmail column

            .AutoFilter.Range.AutoFilter Field:=6, Criteria1:="Y"

            'Copy the column of visible email addresses from wsCopy copy to wsUnique worksheet

            'No need to test here if any records displayed due to

            'previous test for at least one "Y" in column of initial data.

            With .AutoFilter.Range

                .Columns(3).SpecialCells(xlCellTypeVisible).Copy _

                    Destination:=wsUnique.Cells(1, 1)

            End With

            wsUnique.Columns.AutoFit

        End With

        With wsUnique

            'Remove duplicate email addresses from temporary list

            .Columns("A:A").RemoveDuplicates Columns:=1, Header:=xlYes

        End With

        'Remove the name "(Blank)" from the unique list

        'Probably not required now only SendEmail = "Y" included but have left in place anyway

        With wsUnique

            Set rngToFind = .Columns(1).Find(What:="(blank)", _

                    LookIn:=xlFormulas, _

                    LookAt:=xlWhole, _

                    SearchOrder:=xlByRows, _

                    SearchDirection:=xlNext, _

                    MatchCase:=False)

            If Not rngToFind Is Nothing Then

                rngToFind.EntireRow.Delete

            End If

        End With

        With wsUnique

            'Assign unique list of email addresses to a range variable

            Set rngUnique = .Range(.Cells(2, "A"), .Cells(.Rows.Count, "A").End(xlUp))

        End With

        'Set up Outlook once before going into the loop

        'Test if Outlook already open

        On Error Resume Next

        Set objOutlook = GetObject(, "Outlook.Application")

        On Error GoTo 0

        If objOutlook Is Nothing Then

            Set objOutlook = CreateObject("Outlook.Application")

        End If

        With wsCopy

            'Start the loop. Cycle through the temporary Unique list of email addresses

            For Each celUniq In rngUnique

                .AutoFilter.Range.AutoFilter Field:=3, Criteria1:=celUniq.Value

                'Assign visible data only without column headers to a range variable

                With .AutoFilter.Range.Columns(8)

                    Set rngFilt = .Offset(1, 0) _

                    .Resize(.Rows.Count - 1, 1) _

                    .SpecialCells(xlCellTypeVisible)

                End With

                'Create a string with line feeds for the Clients, Projects and Hours.

                'by looping through the visible rows of data.( Chr(9) is a tab.)

                strProjs = ""

                For Each celProj In rngFilt

                    strProjs = strProjs & vbCrLf & "Project" & Chr(9) & ": " & celProj.Offset(0, -1) _

                        & ", " & celProj.Value & vbCrLf & "Hours" & Chr(9) & ": " _

                        & celProj.Offset(0, -6) & vbCrLf

                Next celProj

                'Create a string for the body of the email

                strBody = ""    'Start with zero length string for each email recipient

                With rngFilt

                    strBody = "Hi, " & .Cells(1, 1).Offset(0, -3) & ", " & vbLf & vbLf & _

                    "This week (commencing " & Format(.Cells(1, 1).Offset(0, -7), "MMMM D, YYYY") & ")" _

                        & " you should be working on the following project(s):" & _

                        vbCrLf & strProjs & _

                        vbLf & "If this is not your understanding based on your project PM's " _

                        & "instruction, please inform Patty Yuen asap."

                    'Create a string for the Subject of the email

                    strSubject = "Your staffing for the upcoming week, " _

                        & Format(.Cells(1, 1).Offset(0, -7), "MMMM D, YYYY")

                End With

                Set objMailItem = objOutlook.CreateItem(cstMailItem)

                'set Importance indicator on email message

                objMailItem.Importance = cstImportanceHigh

                'Set Subject of email message

                objMailItem.Subject = strSubject

                objMailItem.Body = strBody

                'Set email recipient (who is resource whose hours are captured in the email)

                objMailItem.Recipients.Add celUniq.Value

                'below is for sending email out immedaitely

                'objMailItem.Send

                'below is for displaying email without sending

                'objMailItem.Display

                'below is to put emails all in Outlook draft folder to be manually sent from there

                objMailItem.Save

            Next celUniq

        End With

        'Return to initial worksheet (code selects worksheet and cell in one line)

        Application.Goto wsPivot.Cells(1, 1)

        'Delete temporary worksheets. (Comment out if you want to see what is in them)

        Application.DisplayAlerts = False

        wsUnique.Delete 'Delete unique list of email addresses worksheet

        wsCopy.Delete   'Delete copy of pivot data worksheet

        Application.DisplayAlerts = True

        'Cleanup of object variables

        Set celUniq = Nothing

        Set celProj = Nothing

        Set rngToFind = Nothing

        Set rngUnique = Nothing

        Set rngFilt = Nothing

        Set wsPivot = Nothing

        Set wsCopy = Nothing

        Set wsUnique = Nothing

        Set objOutlook = Nothing

        Set objMailItem = Nothing

    End Sub

    Was this answer helpful?

    0 comments No comments
  3. Anonymous
    2013-12-31T14:00:05+00:00

    I have posted the Excel 2013 workbook to SkyDrive:

    https://skydrive.live.com/redir?resid=BC0D4DDE2903493A%21166

    I know I am asking a lot, so in advance, thanks.  Feel free to recommend best practices, and any changes as you see fit.

    Purpose

    Accumulate employee projects into an email and send list of projects to employee using Outlook.

    Issue:

    It runs perfectly until it gets about half way through the list of employees. When it gets to that point it loops through and lists all projects to the addressee at that point and does not continue sending emails to others on the list.

    Was this answer helpful?

    0 comments No comments
  4. Anonymous
    2013-12-28T20:07:05+00:00

    Hi.  I can't follow your code to well, so here's just some ideas:

    Set aOutlook = CreateObject("Outlook.application")

    I would move this "out" of the loop, perhaps to the beginning of the code.

    Try to rewrite the logic to remove goto 100.

    Do Until Y > ((N + Z) - 1)

                BODY1 = ...

                   x = x + 1

                Y = Y + 1

    Loop

    Perhaps write it similar to: 

    For Y = N to (N + Z - 1)

      Body = ...

      x = x + 1

    Next Y

    I might make the Subject & Body lines separate.

    If  you have the Tools : Reference set to outlook, perhaps something like this:

    sSubject = " ... "

    sBody = " ... "

       With CreateItem(olMailItem)

            .Importance = olImportanceHigh

            .Subject = sSubject

            .Body =  sBody

            .Recipients.Add whoever @ hotmail.com

            .Send

        End With

    > Dim N As Integer

    Seems to be "standard practice" (??) to use Longs on Rows since the number of rows are rather large.

    Dim R as Long

    Again.. just some ideas to throw out..   :>)

    Was this answer helpful?

    0 comments No comments
  5. OssieMac 48,001 Reputation points Volunteer Moderator
    2013-12-26T23:03:13+00:00

    I am sure that it can be made to work but a bit difficult to understand the format of the workbook without seeing it. Can you post a copy of the workbook. If you have sensitive data then can you make a copy and replace sensitive data with dummy data. Provided there is nothing in the copy of the workbook to allow people to guess the domain for the email address then you should be able replace all Email addresses with a global Find and Replace. eg. if email addresses have a format of RecipientName @ BusinessName.com then you should be able to replace BusinessName with a dummy name.

    To upload a workbook to Skydrive.

    1.Best to zip the file before uploading.

    2.Below is a link for how to post on Skydrive.

    3.There is a link in step 1 to open Skydrive.

    4.You can use the same login name and password that you use for this site.

    5.After uploading the file, Right click it and select sharing.

    6.Don't fill in the form; select "Get a link" in the left column.

    7.Click button Create for others to read.

    8.Copy the returned link (which is highlighted) and paste into your answer on this forum.

    http://windows.microsoft.com/en-US/skydrive/change-access-permissions-faq

    Was this answer helpful?

    0 comments No comments