If your database has a table with "person"
information, odds are it has an email column. If your users collect email
addresses, odds are they want to send emails to persons in the database and
before long, someone's going to get it into their head to send out an email
"blast" or batch.
There are several approaches to this request and over the
years, I've used them all. The easiest solution is to create a function that
returns a semi-colon delimited text string of emails and plunk in into the
recipient's field of the email created in Microsoft Outlook. This is simple to
implement and easy for users to understand, but carries with it the likelihood
that the recipient's spam filter will reject it.
What you really need is an engine to send individualized
emails, one at a time. That's the subject of this month's article, and one
solution (of many potential solutions) is in the attached download. <BulkBatchEmail.zip>
Table Driven Emails
The first step is to set up some tables. It's assumed you
have a table with the person-email information. In the demo this table is
named tblContacts. To implement the demonstration code you'll have to make a
few edits, if your table is not thus named. Simply do a search in all code
modules for the word "tblContacts" and you'll see the edit points you
need to address.
In addition to your own contacts table, you'll need to
import the following tables into your MDB application:
tblEmail: Contains the email content and delimited
tblEmailRecip: Used to display the recipients as
rows in a table
tblEmailAttachment: Lists the attachments for an
tblEmailDefault: A single row with default text to
be used with all emails
I've found that this approach has some advantages. Users
like to look back to see what messages were sent and to whom, so tblEmail
serves as a correspondence history of sorts.
The recipient list behaves as it does in other mail apps,
showing email addresses as a semi-colon delimited list, but the inclusion of
tblEmailRecip adds the flexibility of viewing the addresses as a sorted list
with a lookup to the contact's full name.
Default text may be saved in tblEmailDefault, such as an
email signature and company contact info, as well as a confidentiality
The Forms and Modules
The application has two forms, frmEmail and frmRecip,
which do all the heavy lifting. There are also two code modules, basFnsAPI
and basUtilities, which provide support services. Credit has been given
where code has been borrowed from websites and newsgroup postings.
Both forms are shown below and appear as one might expect an
email application to look. The creativity here is not in the cosmetic
appearance, but in the functionality, which includes the following:
and easy access to the Defaults table. In this example, only two defaults are
given, email signature and company address, but more could be defined and
used. Additionally, one could create a personalized row of defaults for each
after-update event of the Recipient List text box takes action to clean up the
list, removing spaces and replacing commas with semi-colons.
the recipient list opens/loads the Recipient List form with a lookup to the
contacts table. While this list is not editable, one may delete rows from the
pop-up form and see them disappear from the semi-colon delimited list in the
attachments listbox is loaded using the Add and Delete buttons to its left.
The file navigation common dialog box is implemented for this process.
Double-clicking an entry opens the attachment for viewing.
it's not obvious from the UI, the body text will be inserted as HTML. This
allows for some creativity for those who know a little about HTML tags.
most important feature is the use of placeholder tags. In the email body text box
you will see references to <name>, <email sig> and <co
address>. When the email is created, these tags will be replaced with data
that is looked up, so that the text "Dear <name>" will become
something like "Dear Lamont Cranston".
This feature is extensible, of course. We once ran a batch where user's login credentials
were inserted into 400 emails for clients who were being given access to our
new web portal. I simply added a tag with the text <site login> and
modified the code to do the appropriate lookup and substitution.
The beauty of using tags is that they may be employed, or not. If left out of
the body text, the substitution will not take place. Leave off the <co
address> and the company address will not show. Leave off the <name>
tag and you can simply begin the email with "Dear Sirs". (Do we
still start letters that way?)
safety valve. (We'll discuss this in the next section.)
record is locked after the email(s) are sent. Though not shown here, the
controls back-color is toggled to light yellow and their contents are locked
after the request to send has been executed. This avoids the likelihood that
the email will be accidentally sent a second time.
The Big Send
My biggest fear in creating this user interface was that
users might click Send and then regret it for one reason or another.
Accordingly, I engineered (maybe over-engineered) a system of warnings and
Rather than just initiating the send of these merged-data
emails without previewing, I give the user the option to view them or cancel
the entire operation. They see the message shown below, and if they click Yes,
then the emails are displayed, not sent, as illustrated by the screen shot
Here we can see the effect of the tags. The name tag was
replaced with real names, the email signature and company address tags also
show our defined text. Also note that the email body is in HTML format, so
where we added BOLD tags in our defaults, the text is bold.
The Code... In Brief
There is quite a lot of code involved so I won't be
reproducing it all here, but if you get the download, you'll see it includes
code to perform the following functions:
Get the Login Name of the current user.
Open a Find File dialog box.
Loop through items in a listbox
Toggle form controls setting properties as the record is loaded
Programmatically INSERT records into a table.
Programmatically open any file (from attachment list)
Send emails using HTML body style with Outlook automation.
The code used in the download employs Outlook Automation, as
shown in the code snippet below.
' refresh the local body text variable with the template value.
strBody = Replace(sBody, vbCrLf, "<br>")
' lookup the contact name ... if not found, derive from email
strSQL = "SELECT TOP 1 [FirstName] & ' ' & [LastName] AS Contact " & _
"FROM tblContacts WHERE [Email]='" & strRecip & "' "
SetRstA strSQL ' function for loading recordset named rstA
If rstA.BOF And rstA.EOF Then
strName = Left(strRecip, InStr(1, strRecip, "@") - 1)
strName = Nz(rstA!Contact, Left(strRecip, InStr(1, strRecip, "@") - 1))
Set rstA = Nothing
strName = StrConv(Replace(strName, ".", " "), vbProperCase)
' here is where body text tags are replaced with looked up values
strBody = Replace(strBody, "<name>", strName)
strBody = Replace(strBody, "<email sig>", "<br>" & strEmailSig)
strBody = Replace(strBody, "<co address>", "<br>" & strCoAddress)
.To = strRecip
.Subject = sSubject
.BodyFormat = 2 ' olFormatHTML = 2
.HTMLBody = strBody ' use HTMLBody, not Body property.
'.Body = strBody
' loop through listbox, adding attachments to email
For lngItem = 0 To Me!lstAttachments.ListCount - 1
strName = Nz(Me!lstAttachments.Column(1, lngItem), "")
strFile = Nz(Me!lstAttachments.Column(2, lngItem), "")
If strFile <> "" Then
.Attachments.Add strFile, 1, 1, strName
' here is where we decide whether to show or send the email
If fPreviewAll Then
If intPreviewLimit >= cPreviewLimit Then
If fSend = True Then
intPreviewLimit = intPreviewLimit + 1
As was mentioned at the outset, there are other ways to send
an email. One is to loop through your list of addresses and call the DoCmd.SendObject
that is native to Access. This will certainly work, but I don't believe I was
ever able to get it to display HTML body content the way I wanted.
Another send mail method involves CDO. This code is also
very good and not dependent upon having Microsoft Outlook configured for each
user. Below is a partial code example of how CDO might be implemented. It's a
nice alternative to Outlook automation.
Dim sch As String
Dim rrc As String
Dim rrn As String
Dim cdoConfig As Object
Dim cdoMessage As Object
sch = "http://schemas.microsoft.com/cdo/configuration/"
rrn = "urn:schemas:mailheader:disposition-notification-to"
rrc = "urn:schemas:mailheader:return-receipt-to"
Set cdoConfig = CreateObject("CDO.Configuration")
.Item(sch & "sendusing") = 2 ' cdoSendUsingPort
.Item(sch & "smtpserver") = "YourMailServerNameHere"
.Item(sch & "smtpserverport") = 25
Set cdoMessage = CreateObject("CDO.Message")
.Fields(rrn) = strFrom
.Fields(rrc) = strFrom
Set .Configuration = cdoConfig
.ReplyTo = strFrom
.From = "Network Management <" & strFrom & ">"
.To = sRecipName & " <" & sRecipEmail & ">"
.Subject = sSubject
.HTMLBody = sBody
If Dir(sAttach) <> "" And sAttach <> "" Then .AddAttachment (sAttach)
Set cdoMessage = Nothing
Set cdoConfig = Nothing
SendEMailCDO = Err.Number
Room for Customization
If you implement this email form, the next thing you'll
likely be asked to do is to provide an interface for loading a list of emails.
This demo application doesn't include such a device but the screen-shot that
follows shows one I had built for one of my clients.
For this client, the key to selecting email addresses was
the category they were assigned to. I also supplied a text box for a cut-off
date, to exclude really old records. The list box updates automatically when
the category is selected or a date edited.
The Load Emails button does nothing except loop through the entries
creating a semi-colon delimited text string of email addresses, and then plunks
it into the Recipients text box on the email form. It's handy, but a very
As long as emails are with us, the need for sending out
batches of them will be too. This feature has been embedded into most of my
applications. I have found it to be invaluable and I suspect you will too.
See All Articles by Columnist Danny J. Lesandrini