1

not sure how to best title this, but I have a sheet from which I loop through each row and create an email for each row. Attachments are based on the Division name. Currently, it creates an email for every row, so if one person under Name has, say 8 divisions, they will receive 8 emails, each with a different attachment. This is annoying people, so I want to have it now loop (maybe nested?) and if if finds the same Name, then create one email for that Name, with all their Division reports attached.

To make it easier, I have set the list so that any dupe Names are all grouped together. In this example, I would want it to create one email to the Name Sample Sample1, with attachments for Widgets and Doorknobs. Then for the rest, they would each get their usual one email. I have tried for hours to get this to work, but simply do not have enough VBA knowledge to make this work. I can do it in Excel itself with formulas, basically saying that if A2=A3, then do this. But I need help to get this to happen in VBA. Please see the image.
Update: I have updatedthe below code I have put together using the factoring method shown to be by Vityata. It runs, but creates dupes of each email.

Sheet Example

Option Explicit

Public Sub TestMe()

Dim name            As String
Dim division        As String
Dim mail            As String
Dim dict            As Object
Dim dictKey         As Variant
Dim rngCell         As Range

Set dict = CreateObject("Scripting.Dictionary")

For Each rngCell In Range("b2:b4")
    If Not dict.Exists(rngCell.Value) Then
        dict.Add rngCell.Value, rngCell.Offset(0, -1)
    End If
Next rngCell

For Each dictKey In dict.keys
    SendMail dictKey, dict(dictKey)
Next dictKey

End Sub

Public Sub SendMail(ByVal address As String, ByVal person As String)
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

Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")

sigString = Environ("appdata") & _
            "\Microsoft\Signatures\Test.htm"

            If Dir(sigString) <> "" Then
     signature = GetBoiler(sigString)
     Else
     signature = ""
    End If


strdir = "z:\"


strBody = "<Font Face=calibri>Please review the attached report for your department." 

For Each address In Columns("B").Cells.SpecialCells(xlCellTypeConstants)

    If cell.Value Like "?*@?*.?*" And _
       LCase(Cells(cell.Row, "C").Value) = "yes" Then
        Set OutMail = OutApp.CreateItem(0)


    With OutMail
        strName = Cells(cell.Row, "a").Value
        strName1 = Cells(cell.Row, "d").Value
        strName2 = Left(strName, InStr(strName & " ", " ") - 1)
        strFilename = Dir("z:\*" & strName1 & "*")

            .To = cell.Value
            .Subject = "Monthly Budget Deficit Report for " & strName1
            .HTMLBody = "<Font Face=calibri>" & "Dear " & address & ",<br><br>"
            .Attachments.Add strdir & strFilename
            .Display  'Or use Send
    End With

        Set OutMail = Nothing
    End If
Next cell

End Sub



Function GetBoiler(ByVal sFile As String) As String
Dim fso As Object
Dim ts As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(sFile).OpenAsTextStream(1, -2)
GetBoiler = ts.ReadAll
ts.Close
End Function
  • You're probably going to want to use the excel table as an [ADO datasource](https://stackoverflow.com/questions/2484516/vba-create-adodb-recordset-from-the-contents-of-a-spreadsheet), then write a loop that selects each distinct person and email address, and then selects all departments that match those items, and adds the appropriate attachments. Is it a requirement to stay in Excel? – Chris Meurer Nov 30 '17 at 17:02

1 Answers1

0

A question like this can be summarized to the following: How do I avoid duplicate values in VBA, as far as you do not want to send twice the same e-mail to the same address.

Thus, imagine the following data:

enter image description here

You do not want to send the email twice to Mr. Test and Mr. Test2. What is the alternative? Try to build a dictionary, as a key the unique mail column. Then refactor your code, sending code only to the people that "made it" to the dictionary. You need to refactor your code, thus at the end you get something like this:

Option Explicit

Public Sub TestMe()

    Dim name            As String
    Dim division        As String
    Dim mail            As String
    Dim dict            As Object
    Dim dictKey         As Variant
    Dim rngCell         As Range

    Set dict = CreateObject("Scripting.Dictionary")

    For Each rngCell In Range("C2:C6")
        If Not dict.exists(rngCell.Value) Then
            dict.Add rngCell.Value, rngCell.Offset(0, -1)
        End If
    Next rngCell

    For Each dictKey In dict.keys
        SendMail dictKey, dict(dictKey)
    Next dictKey

End Sub

Public Sub SendMail(ByVal address As String, ByVal person As String)
    Debug.Print "Mr./Mrs. " & person & ", here is your email -> " & address
End Sub

This is what you get:

Mr./Mrs. Test, here is your email -> test@t.t
Mr./Mrs. Test2, here is your email -> test2@t.t
Mr./Mrs. Test3, here is your email -> test3@t.t

The idea of the refactoring, is that you separate the "reading-from-Excel" logic from the "Send email" logic. In the "reading-from-Excel" logic you will only read those parts, which are unique and in the "Send email" you will send mail to anyone who has passed the reading logic.

Vityata
  • 42,633
  • 8
  • 55
  • 100
  • Thank you, you went way over my head though! I did get it to run from the immediate window and it did as you noted. Where I am lost, though, is how I integrate this into the code I already have, so that it creates the emails with all of the text, attachments, and so on? I was originally thinking I could just use if/then stmts, but could not make it happen. Also, how do I get this so it launches from a normal macro button? Sorry, still new to this! – learningthisstuff Nov 30 '17 at 17:40
  • @learningthisstuff - the idea is to divide your code into two parts. It is not an obvious solution, but once you have some experience it will be coming naturally. In the part which sends the mail, you should be putting the logic for `With OutMail`. It will not come easy, you would probably need about a day or two to achieve it. Read more about functions and parameters in VBA. – Vityata Dec 01 '17 at 08:57
  • thank you very much, but given that I am brand new to this and am looking at code I do not understand at all, I will look for another approach and abandon the vba idea. I thought it would just mix in somehow. I wish I knew or understood this stuff, as it is awesome what you all can do! Thanks anyway Vityata! – learningthisstuff Dec 01 '17 at 14:27
  • BTW, to give you an idea of how little I know, it took me over two weeks just to get that code above, with help! haha. – learningthisstuff Dec 01 '17 at 14:35
  • @learningthisstuff - the learning curve by VBA will is steep, don't worry. Watch some videos, see some more code daily, in a month you will be much better! :) – Vityata Dec 01 '17 at 15:09
  • Hi Vityata, I did what you said and have been working on this since Saturday. I think I get what you did now; your code creates the dictionary values and then passes them to a called sub (macro), which are called address and person...am I close? I actually got the code to work and launch my macro! I started playing with placing the address and person values in different places to see what they did. Can you assist me with getting this to run further? I updated my code above. It runs but creates the emails twice. It also errors out with 'signaure' not being defined. Still trying! Thanks! – learningthisstuff Dec 05 '17 at 15:31
  • @learningthisstuff - Start debugging with `F8` and use the immediate window and the watch windows to see the values of the variables. Concerning your code, you do not need to rewrite from scratch `Public Sub SendMail(ByVal address As String, ByVal person As String)`. The idea of this function is simply to write the email, not to read anything from the excel file. It gets what it needs as parameters. Good luck! – Vityata Dec 05 '17 at 15:49
  • I got it to create some emails but it keeps erroring with the 'signature' word. It says variable not defined. But I have never had an error with that before, only when the sub is called from your new code. What am I doing wrong, as it will not call the function for grabbing the correct signature? Thanks – learningthisstuff Dec 05 '17 at 17:45
  • My new code has `Option Explicit`. You have two options - delete the `Option Explicit` line or be forced to declare each variable like this `dim signature as String` or whatever. The choice is yours. @learningthisstuff – Vityata Dec 05 '17 at 17:47
  • Also, your code seems to be working to identify the names to add, BUT what I need it to do is create ONE email to the dupe names, but add each different attachment as defined by the Division column. So, sample1 would get one email but with two reports attached. I am not sure how to do that? – learningthisstuff Dec 05 '17 at 17:50
  • Awesome, that option explicit thing made the signature function work! So cool to see the effects of these small changes! – learningthisstuff Dec 05 '17 at 17:53
  • @learningthisstuff - what did you do? Deleted `Option Explicit` or declaring the variables. (Just curious). – Vityata Dec 06 '17 at 09:17
  • I deleted the Option Explicit. However, I am still stumped, as I cannot figure out how to get this to: 1. run so that it creates one email with two or more attachments going to the same person. 2. get it to run only once, as it loops through multiple times...first loop is in your code, next loop is in mine. I tried to remove mine by adding to your dictionary (the division column) but I cannot get it to work. Looked all over, but it keeps saying I have too many arguments, or that the variable is already used in the dictionary. Can you help me with these? – learningthisstuff Dec 06 '17 at 19:37
  • Perhaps I should post this as a new question? I think I am close, but do not have the knowledge to get it all the way. I even tried an exit loop line in my code but that did not work either (the part where it says, 'for each cell....' – learningthisstuff Dec 06 '17 at 21:40
  • @learningthisstuff - a new question should be better! :) In general, the problem is that you are looping in your code inside my code, but you can only benefit from the new one :) Good luck! – Vityata Dec 06 '17 at 21:46
  • @learningthisstuff - by deleting `Option Explicit` you have chosen the dark side! :) It is a bit "easier" to program this way, but after a while you would realize it is really a bad practice. Thus, it is probably better to start using it and declare new variables. – Vityata Dec 06 '17 at 21:47
  • I did actually try to declare 'Signature' as various things...object, variant, string, long, etc. Nothing would work! – learningthisstuff Dec 06 '17 at 22:30
  • I posted my new question here: https://stackoverflow.com/questions/47685381/create-email-from-excel-using-dictionary-to-match-attachments-to-email. Feel free to edit it if needed. – learningthisstuff Dec 06 '17 at 23:48
  • Edited question, so new link: https://stackoverflow.com/questions/47685381/create-email-from-excel-using-a-loop-to-go-through-rows – learningthisstuff Dec 07 '17 at 15:13