|
Home
|
Download | Purchase | Shopping Cart | About Us | Contact Us | Support | Other Products |
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:
Email Properties - Here you set the various email headers, including the mail server, From Address, From Name, To Email Address and To Name. If your mail server requires authentication, you can enter your username and password. You can also log each email as it is sent, and record if the email was successfully sent or failed.
Email Content - The email content tab allows you to specify the email subject and body. You can also line check your email for lines over 76 characters (some email readers don't like lines longer than 76 characters, as specified by RFC822).
Database Connection - The database setting section includes the connection string to the database, and the sql used to retrieve the dataset of email address information from the database. If you know your connection string, you can enter it directly in the Connection String textbox.
Testing Email Data - Once you have a connection string and relevant sql, you can test your email. By clicking "Load Db Schema", RapidMailer will retrieve the columns from your database. Once you have the columns, enter in some testing information and you can send test emails.
Saving your Settings - Under the File menu, you can find a number of commands. Most importantly, Saving and Loading previously saved settings.
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.
We can import aspNetEmail into our project. In Visual Studio .NET, under the solution explorer, right-click the project and select Add | Add Existing Item. Browse to the location you installed aspNetEmail at, which by default, is C:\Program Files\AdvancedIntellect\aspNetEmail and select aspNetEmail.dll.
Or we can can open up the folder C:\Program Files\AdvancedIntellect\aspNetEmail, and Drag-n-Drop the aspNetEmail.dll assembly in our project. Here is a picture adding aspNetEmail.dll to our project.
Adding aspNetEmail.dll to The Project

Next, we need to set a reference to aspNetEmail. To set a reference:
In solution explorer, right-click References, and select Add Reference.
Click the Browse button, and browse to aspNetEmail, and click Open. Click here to see a large image of setting the 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