mail for ASP.NET email smtp mail for ASP.NET email smtp
 
    
    

WebMailer:

Sending 1000s of emails from a web page without timing out.

Download C# and VB.NETSource Code

Run Live Demo
 

Summary

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 mailing.

Disclaimer:
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.
or
b)Do not have admin level access to install the service.

Therefore, as a work-around, the technique described below, may be employed.

Overview
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 Image 1.

Note
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, specifically the 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()
{	

	//initialize 
	msg.FromAddress = "me@mydomain.com";
	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 found below:

private void OnRowSend( object sender, MergedRowSentEventArgs e )
{
	try
	{
		//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
			EmailCounter++;
			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 task:

bool debugging = false;

	if( debugging )
	{
		Send();
		Response.Write( "completed." );
	}
	else
	{
		ThreadStart start = new ThreadStart( Send );
		Thread t = new Thread( start );
		t.Priority = ThreadPriority.Lowest;
		t.Start();

		//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.

Frames Page
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=">
Every time this page is generated, it uses javascript to update the status.aspx 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 following client side javascript from the server side:

private string GetClientJavaScript( string statusLine, int statusWidth, string percentComplete )
{
	StringBuilder sb = neadsw StringBuilder();
	sb.Append( "<script language=\"javascript\">\r\n" );
	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>" );
	return sb.ToString();
}
			

This javascript, will execute in the client browser, from a hidden frame, updating the visible, status frame.

Finishing Up
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:

  • Row Id
  • Timestamp
  • Email Address
  • And if the Send was successful for that address
An example of the log can be found below.

0, 22:46:9.433,user0@myfakedomain.com,True
1, 22:46:9.433,user1@myfakedomain.com,True
2, 22:46:9.443,user2@myfakedomain.com,True
3, 22:46:9.443,user3@myfakedomain.com,True
4, 22:46:9.443,user4@myfakedomain.com,True
5, 22:46:9.443,user5@myfakedomain.com,True
6, 22:46:9.443,user6@myfakedomain.com,True
7, 22:46:9.453,user7@myfakedomain.com,True
8, 22:46:9.453,user8@myfakedomain.com,True
9, 22:46:9.453,user9@myfakedomain.com,True
10, 22:46:9.453,user10@myfakedomain.com,True

Conclusion
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    
downloadable product.
 
aspNetEmail
Voted Number 1 ASP.NET Email Control
Voted Best Email Control


aspNetEmail
Runner Up ASP.NET Email Control
Voted Runner Up Email Control


aspNetEmail
Voted Number 1 ASP.NET Email Control
Voted Best Email Control


aspNetEmail
Voted Number 1 ASP.NET Email Control
Voted Best Email Control


aspNetEmail
Voted Number 1 ASP.NET Email Control
Voted Best Email Control


aspNetTraceRoute
Voted Runner Up Networking Control
Runner Up - Networking Control