Building a Newsletter Emailer (RapidMailer)

Download the Application And Source Code (Free)

A number of people have written and asked me how I mail out my newsletters for www.123aspx.com and www.kbalertz.com. It hasn't been easy, and I've used various components, techniques, and procedures, and when you are sending out a million emails a month, things have to be just right, or you get a lot of unhappy recipients.  Currently I use a command line interface to do this, but I've turned it into a more generalized GUI for other people. The following article will explain how to send out hundreds of thousands of personalized emails, from a dataset, using a 100% .NET managed application.  In this example, we will be creating and sending out a fake invoice. I'll first begin by discussion an overview of the application, and then delve into the code behind the application.

Tools Needed

I built this application using Visual Studio .NET and the Email component from www.aspNetEmail.com. Their email component is available as a FREE download for their evaluation version. The evaluation version contains all of the functionality of the real version, so we will be using evaluation version here.  If you download the code for this article, you will also need to download the latest version of aspNetEmail from www.aspNetEmail.com.

Overview

Currently I use a command line interface for sending out my newsletters. I built a command line because I wanted to send the emailed form the NT scheduler, but the code is pretty specialized and specifically met my needs. Because a number of people have requested more generic functionality, I modified  my command line code, and converted it, to an easy to use GUI. The GUI is made up of 5 different sections:

Here are some screen shots of RapidMailer.

Email Properties

Email Content
c

Database Connection

Test Email Data

Mail Merge Basics

On the Email Properties tab, you will notice a lot of '##' symbols. This is an interesting feature of aspNetEmail that makes mail merges' a snap.  When you send off the emails, aspNetEmail will find every dataset column name, surrounded by '##', and replace that value with the actual value found in the dataset.  For example, in the 'TO Address Field' we have the value ##Email##. aspNetEmail will look for a column in the dataset named Email and then replace ##Email## with the value found in the dataset. It works like a charm and has saved me hours of programming.  Another section worth mentioning is the body section. Perhaps you already have a template made up in Notepad or a html file in your favorite HTML editor. Using the 'Load From File' button, you can load this content into the email body.

Installing aspNetEmail

Now that you know how the application works, let's install aspNetEmail and start going through some of the code.

Because we are using the SMTP engine provided by aspNetEmail, we need to download and install it on our machine. You can download the latest free version from http://www.aspnetemail.com/Download.aspx . After you download the .msi install file, run the wizard, and accept the defaults. Here are some screenshots of the wizard.

Welcome

Install Folder

Complete

Now that we have the aspNetEmail assembly installed, let's add it to our visual studio project. Because I like having assemblies inside of the same visual studio project, first we want to add the assembly to our project.  There are two popular ways of adding assemblies to your project.

Adding aspNetEmail.dll to The Project

 

Next, we need to set a reference to aspNetEmail. To set a reference:

Delving into the Code

The GUI is really just an interface to seeing the properties exposed by aspNetEmail. So our code will be pretty simple. Let's take a look at some of the code behind the screens.

Email Properties

Our 'TO Address' and 'TO Name' are actually fields in the database, so we simply set them to ##Email##, and ##FirstName## respectively. The code to set these properties looks like

          //set the mailserver
	_Msg.Server = txtMailServer.Text;

	//set the from properties
	_Msg.FromAddress = txtFromAddress.Text;
		
	//if there is a from name, set it to the mail message
	if ( txtFromName.Text.Length>0)
	{
		_Msg.FromName = txtFromName.Text;
	}

	//set the to field
	if ( txtToName.Text.Length>0)
	{
		//set both the ToAddress and the ToName
		_Msg.AddTo( txtToAddress.Text, txtToName.Text );
	}
	else //only set ToAddress
	{
		_Msg.AddTo( txtToAddress.Text );
	}

            

The really interesting code that we use on this tab, is for logging the emails. Logging the emails is performed by trapping an event that is raised from aspNetEmail. To do this we need to

We declare our class level aspNetEmail on the following line private EmailMessage _Msg = new EmailMessage();We use a class level variable so we can wire up to the event MMRowProgress, which tells us the individual status of each email being sent from the database.  We wire up the event using the code _Msg.MMProgress += new EmailMessage.MMRowProgress( this.EmailMessage_OnRowProgress );.  One thing to note, is that because we are not using a multithreaded application, trapping this event is a threadblocking call. Therefore, we want to make sure our code is very efficient because the mail server connection is being held open until this call has been executed. Now that we have our events wired up, let's go ahead and log each email to the WinForm status bar and to a text file using the following code:

	private void EmailMessage_OnRowProgress( DataRow dr, bool Success )
	{
		//update the total sent
		_TotalSent++;

		//update the status bar...
		statusBar1.Panels[0].Text = _TotalSent.ToString() + " of " + _EmailCount.ToString() + " sent";

		string emailField = txtToAddress.Text.Replace("##",string.Empty );
		//say what email we are on..
		if ( Success )
		{
			statusBar1.Panels[1].Text = "Success: " + dr[ emailField ].ToString();

			//check for logging
			if( chkLogEmails.Checked ){ _Log.Write ( "true," + dr[ emailField ].ToString() + Environment.NewLine );}
		}
		else
		{
			statusBar1.Panels[1].Text = "Failed: " + dr[ emailField ].ToString();

			//check for logging
			if ( chkLogEmails.Checked){ _Log.Write( "false," + dr[ emailField ].ToString() + Environment.NewLine );}

		}

		statusBar1.Refresh();
	}

Database Connections

By default we can use 1 of two different providers to load our emails, OleDb ( for Microsoft Access) or  SQL Server.  If you are connecting to Access, you can browse to the database file. If you are using Sql Server, you can enter in the individual connection string properties in each text box.  To build the connection string, we use some pretty simple logic checking, as found below.

		
	private void BuildSqlConnString()
	{
		string ConnString = "Application Name=RapidMailer;";
		ConnString += "Data Source=" + txtSqlServer.Text + ";"; 
		ConnString += "Initial Catalog=" + txtSqlDatabase.Text + ";";
		if ( chkSqlTrusted.Checked )
		{
			ConnString += "Trusted_Connection=yes;";
		}
		else
		{
			ConnString += "Password=" + txtSqlPassword.Text + ";";
			ConnString += "User ID=" + txtSqlUsername.Text + ";";
		}
		txtDbConnection.Text = ConnString;
	}
		

Testing Email Data

To test our email data, we need to connect up to the database and, using the Sql as defined on the Database Connection tab, download the database schema. To download the schema, we need to use either an OleDbDatAdapter or a SqlDataAdapter. Again, we need to use our connection string information to determine this. Once we have the database schema, we can use the schema to create a datagrid that allows the user to enter the test information.  The resulting information can then be sent,  as a datatable, to the aspNetEmail object and emailed.  Here is the code to download the database schema.

        private DataTable GetDataSchema()
	{
		DataSet oDataSet = new DataSet();
		DataTable dtSchema = new DataTable();            
			
		string sqlText = txtSql.Text;
		string sConn = txtDbConnection.Text;

		try
		{
			if ( optDbOleDb.Checked )
			{
	  
			//Create the connection and command objects
			OleDbConnection  oConn = new OleDbConnection(sConn);
			OleDbDataAdapter oDa = new OleDbDataAdapter(sqlText, oConn);
            
			//Fill the dataset with the results of the query
			oDa.FillSchema(dtSchema,  SchemaType.Source);
			}
			else
			{
				//use the sql provider
          
				SqlConnection oConn = new SqlConnection(sConn);
				SqlDataAdapter oDa = new SqlDataAdapter(sqlText, oConn);
            
				//Fill the dataset with the results of the query
				oDa.FillSchema(dtSchema,  SchemaType.Source);
			}

		}
		catch( Exception DbEx)
		{
			MessageBox.Show( "The following exception occurred\n" + DbEx.Message, "Schema Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
		}

		return dtSchema;

	}
        
        

Sending the Emails

Now that we've set our email properties, loaded our data, and set logging, starting the process is a piece of cake.  The code to actually perform is so easy, it almost becomes a typing exercise.

       
        	private void SendEmails(DataTable EmailData )
	{
		if( ( EmailData != null ) && ( EmailData.Rows.Count > 0 ) ) //send the emails
		{
			//load the datatable
		
			//set the mailserver
			_Msg.Server = txtMailServer.Text;

			//set the from properties
			_Msg.FromAddress = txtFromAddress.Text;
	
			//if there is a from name, set it to the mail message
			if ( txtFromName.Text.Length>0)
			{
				_Msg.FromName = txtFromName.Text;
			}

			//set the to field
			if ( txtToName.Text.Length>0)
			{
				//set both the ToAddress and the ToName
				_Msg.AddTo( txtToAddress.Text, txtToName.Text );
			}
			else //only set ToAddress
			{
				_Msg.AddTo( txtToAddress.Text );
			}

			//set the Email Subject
			_Msg.Subject = txtSubject.Text;

			//check to see if we are sending as html
			if ( chkHtml.Checked )
			{
				_Msg.BodyFormat = MailFormat.Html;
			}
			else
			{
				_Msg.BodyFormat = MailFormat.Text;
			}
			
			//check for server authentication
			if ( chkAuth.Checked )
			{
				_Msg.Username = txtUsername.Text;
				_Msg.Password = txtPassword.Text;
			}

			//set the body
			_Msg.Body = txtBody.Text;
			//check to see if we are logging
			if ( chkLogEmails.Checked)
			{
				_Log = File.CreateText( txtLogFile.Text );
			}

			if ( _Msg.SendMailMerge( EmailData ) )
			{
				MessageBox.Show("All done sending emails.\n" + _Msg.MailMergeSuccess.Count.ToString() + " emails were sent.", "Completed");
			}
			else
			{
				MessageBox.Show( "The following error occurred sending the emails\n" + _Msg.LastException().Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
			}

			if ( chkLogEmails.Checked ) //close the log writer
			{
				_Log.Close();
			}

		}
		else
		{
			MessageBox.Show("The datasource of emails must contain at least 1 row of data.", "Empty Datatable of Emails");
		}
	}

		

The code is standard emailing code. We are setting properties for the mail server, the TO and FROM address, subject line, and body. The one interesting piece of code is
msg.SendMailMerge( (DataTable)dataGrid1.DataSource)
Using the SendMailMerge() method of aspNetEmail, we actually pass in the datatable of information and aspNetEmail does all the heavy work of pairing up email addresses, and finding and replacing datatable column names with the equivalent value

 

Saving our Settings

Probably the most interesting part of our code, is saving the settings. This is done by creating a UserSettings object and serializing it out to disk. Let's start by looking at out UserSettings object.
	public class UserSettings
	{
		public string Server;
		public string FromAddress;
		public string FromName;
		public string ToAddress;
		public string ToName;
		public string Subject;
		public string SqlEmail;
		public bool IsHtml;
		public bool Auth;
		public string Username;
		public string Password;

		//database settings
		public string DbConnectionString;
		public string DbProvider;
		public string DbSqlServer;
		public string DbSqlDatabase;
		public string DbSqlUsername;
		public string DbSqlPassword;
		public bool DbTrusted;

		//logging
		public bool LogEnabled;
		public string LogPath;

	}
	

The UserSettings class will simply hold all of our values, such as the mail server address, From Address, To Address, database connection string, etc...  To serialize this object out to disk, we use the XmlSerilize class that is part of the System.Xml.Serialization namespace. Here is the code to accomplish this task.
      			
      	//Serialize the object
	UserSettings us = new UserSettings();
	//set all of the usersettings
	//for example: us.FromAddress = txtFromAddress.Text;
	//serialize UserSettings Object to disk
	XmlSerializer xmls = new XmlSerializer( typeof( UserSettings ) );
	StreamWriter sw = new StreamWriter( _SettingsDirectory + "\\" + txtSettings.Text + ".xml");
	xmls.Serialize( sw, us );

	//close the serializer
	sw.Close();
	sw = null;
	xmls = null;

We pass the UserSettings object, along with a StreamWriter, to the serializer object, and it will do all the work for us.  Our settings are now serialized out to disk as an easy to read XML file.

Conclusion

In conclusion we've seen how easy it is to build a mass mailer, called RapidMailer. This technique has been extremely stable and is in current use my two websites,  www.123aspx.com and www.kbAlertz.com. We've seen how we can email a dataset of email addresses and perform a mailmerge at the same time.  If you have any questions, feel free to write me at dave@123aspx.com