The following article will demonstrate how to send 1000's of emails from a web
page without worrying about script or server timeouts. This application also
provides a status page to provide the user with the current status of the
The correct way to implement the technique, described below, is to write a
multi-threaded windows service, which handles sending the emails. However, many
of my customers do not:
a) Either have console level access to the box to run the service.
b)Do not have admin level access to install the service.
Therefore, as a work-around, the technique described below, may be employed.
Webmailer works by using aspNetEmail to perform a mail merge, and send the
resulting emails on a background thread. Once the thread starts, the user is
directed to a frames page, which consists of two frames: a) A status frame, and
b) a polling frame. The polling frame polls the background thread, and updates
the status frame with information about the mailing. Once the mailing is
complete, the user can view the log of the mailing.
To see a graphic image of this overview, see
To compile and use this application, you will need to download a copy of
aspNetEmail and upload it to the /bin directory of your application.
aspNetEmail can be downloaded from www.aspNetEmail.com/download.aspx.
Starting The Application
The application starts with default.aspx. Default.aspx contains the methods
necessary to perform the mail merge and to start the email sending on a
secondary thread. Let’s look at some of the mail merge code.
Mail Merge Code
The mail merge code relies heavily on the built-in capabilities of aspNetEmail,
SendMailMergeToMSPickup() method. This method accepts a DataTable of
email information (Email Address, First Name, Last Name, etc.). aspNetEmail
will merge this information with the email itself. The Send() method, for
sending the mailmerge, can be found below.
public void Send()
msg.FromAddress = "firstname.lastname@example.org";
msg.FromName = "Billy Bob";
msg.AddTo( "##EmailAddress##" );
msg.MSPickupDirectory = @"C:\temp\tempPickupDirectory\";
//set the row progress event
msg.MergedRowSent += new MergedRowSentEventHandler( OnRowSend );
msg.Subject = txtSubject.Value;
msg.Body = txtBody.Value;
//get the data
DataTable emailData = GetDataTable();
//send the emails
msg.SendMailMergeToMSPickup( emailData );
For every email that is sent, aspNetEmail raises a MergedRowSent Event, which
we’ve wired up with OnRowSend. In this event, we keep track of the number of
emails sent, which email is currently being sent, and if we want to cancel the
mail merge process. All of this information is stored in session variables so
we can access this information on other parts of our website. This code can be
private void OnRowSend( object sender, MergedRowSentEventArgs e )
//create a csv report, that could be used for checking for email sends
//add results to the session object, and update the counter
//lock the session object
lock( Session.SyncRoot )
//update the report
( (StringBuilder)Session[ "EmailReport" ]).Append( e.Row[ "ID" ].ToString() + ", " + GetTimeStamp() + "," +
e.Row["EmailAddress"].ToString() + "," + e.Success.ToString() + "<BR>" );
//update the email counter
Session[ "EmailCounter" ] = EmailCounter;
//update the status line
Session[ "StatusLine" ] = "Just processed " + e.Row[ "EmailAddress" ].ToString();
//check to see if the mailmerge has been canceled
if( Boolean.Parse( Session[ "CancelMailMerge" ].ToString() ) )
msg.CancelMailMerge = true;
catch( Exception ex )
lock( Session.SyncRoot )
Session[ "MailMergeException" ] = ex;
Because this is a mult-thread application, we have to be careful about updating
our data, so that it doesn’t become corrupt. To handle this, we lock our data
using the C# keyword ‘lock’ or the VB.NET keyword ‘SyncLock’. Locking our code
makes sure only a single thread can access it at a time.
Spawning a New Thread
Now that we have most of the hard work out of the way, we simply need to
execute the mail merge on a secondary thread. The following code performs this
bool debugging = false;
if( debugging )
Response.Write( "completed." );
ThreadStart start = new ThreadStart( Send );
Thread t = new Thread( start );
t.Priority = ThreadPriority.Lowest;
//give it a second or two to start
Thread.Sleep( 1000 );
Response.Redirect( "frames.htm" );
To start a new thread, we create a new ThreadStart object, that contains a
pointer to our Send() method. We pass our ThreadStart object, named start, to a
brand new thread, and start the thread by calling t.Start(); Once the thread
has started, we redirect the user to our frames page.
A note about exceptions: If any exceptions occur in our code, because they are
occurring on a background thread, we will NOT know if any occurred. To handle
this, we test our code by first executing it on the main thread. This is
handled by the bool variable named debugging. If debugging = true, we simply
execute our Send() method. When we are testing our Send() method, the DataTable
of email information should be sized down to only a few rows to prevent page
timeouts. Once we are happy with out code, we can set debugging=false, and run
our complete mailmerge.
Now that our mail merge is happily executing on a background thread, We need to
monitor our session variables to get a progress report of our mail merge. To do
this, without ugly page refreshes, we implement a hidden frame technique. The
frames page consists of two frames: a) A status frame named status.aspx, and b)
a polling frame named getstatus.aspx. Getstatus.aspx is in a hidden frame, and
it refreshes itself every 3 seconds using the meta tag
<META HTTP-EQUIV=Refresh CONTENT="3; URL=">
page with the current status of our background thread. The status information
we are trapping includes
Last Email Sent
The number of emails sent
And the percentage of mail merge complete.
This information can be obtained form the session variable we updated in our
EmailMessage_OnSend event. Using the session variables, we generate the
StringBuilder sb = neadsw StringBuilder();
sb.Append( "var statusLine = \"" + statusLine + "\";\r\n" );
sb.Append( "var percentComplete = \"" + percentComplete + "\";\r\n" );
sb.Append( "var statusWidth = " + statusWidth.ToString() + ";\r\n" );
sb.Append( "parent.statusWindow.Update( statusLine, statusWidth, percentComplete);\r\n" );
sb.Append( "</SCRIPT>" );
updating the visible, status frame.
Whew, with all the hard work done, all that’s left to do is to view the log of
our mail merge. By navigating to getlog.aspx, we write out the session variable
used to keep a log of every email sent. The log is actually a CSV (comma
separated value string) containing:
An example of the log can be found below.
And if the Send was successful for that address
We’ve seen how easy it is to execute a mass mailing on a background thread
without script or server timeouts. Once we’ve started the mailing on a
background thread, we monitor the progress using a frames page. After the
mailing has completed, we can view the log of each individual email.
The box is not shipped.
aspNetEmail is a