Sending Emails Asynchronously in C#
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

We're gearing up to begin sending out our email newsletter here at Eggheadcafe.com and so we've been experimenting with different ways to send out a large number of emails to subscribers. Since the SMTP class does not expose an asynchronous version of the SmtpMail.Send(Message) method, (e.g. BeginSend / EndSend) we either have to use a commercial or open source component that does, or roll our own.



Fortunately the Framework comes to our rescue here with callback delegates and the BeginInvoke method, which (unfortunately) is only documented by Microsoft for Windows Forms controls (making developers think that that's the only place it's actually available to use). To add to the potential confusion, the BeginInvoke / EndInvoke methods for Windows Forms controls are actually a completely different animal from what we are discussing here with AsyncCallbacks. Suffice to say that the methods we want to use here are in fact available anytime you use a delegate. Are you confused now? I certainly was!

Here’s a class with properties to make it useful for sending out html emails asynchronously. First the code, then the discussion:
using System;
using System.Threading;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Web.Mail;
using System.Diagnostics;
using System.IO;
namespace eggheadcafe.util.smtp
{
 /// <summary>
 /// USAGE:
/// EmailUtil eu = new EmailUtil(); 
/// eu.HtmlFilePath=@"c:\temp\file.htm";
/// eu.Subject ="test message";
/// eu.SmtpServer="mail.myserver.com";
/// eu.FromEmail="you@yourserver.com";
/// (loop here through your datatable etc. of email recipients ---)
/// eu.SendEmailAsync("daddy longlegs", "dlonglegs@yahoo.com");
/// eu.SendEmailAsync("Mama Bear", "mamabear@mindless.com");
/// eu.SendEmailAsync("Bill Clinton", "billy@clinton.org");
/// eu.SendEmailAsync("Jack Spratt", "sprattsksy@yayhey.com");
 /// </summary>
 public class EmailUtil 
 {
       private string htmlFilePath;
  public string HtmlFilePath 
  {
   get
   {
             return htmlFilePath;
   }
   set
   {
             htmlFilePath=value;
   }
  }
  private string subject;
  public string Subject
  {
   get
   {
    return subject;
   }
   set
   {
    subject=value;
   }
  }

  private string smtpServer;
  public string SmtpServer
  {
   get
   {
    return smtpServer;
   }
   set
   {
    smtpServer=value;
   }
  }

  private string fromEmail;
  public string FromEmail
  {
   get
   {
    return fromEmail;
   }

   set
   {
    fromEmail=value;
   }
  }

  public string strBody=String.Empty;

  public string SendEmail(string name, string emailAddress)
  {
   if(strBody==String.Empty)
   {
    try
    {
     using (StreamReader reader=new StreamReader(this.HtmlFilePath)) 
     {      
      strBody = reader.ReadToEnd();  
     }  
    }
    catch(Exception ex)
    {
     throw new Exception("error reading HTML File" + ex.Message);
    }
   }
   try
   {
    MailMessage Message = new MailMessage();
    Message.BodyFormat= MailFormat.Html;
    Message.To = emailAddress;
    Message.From =  this.FromEmail;
    Message.Subject = this.Subject;
    Message.Body = strBody;           
    SmtpMail.SmtpServer =  this.SmtpServer;
    SmtpMail.Send(Message);
   }
   catch(System.Web.HttpException ehttp)
   {
    throw new Exception("Send error" + ehttp.Message);          
   }
  return "sent" + name;
  }
  public delegate string SendEmailDelegate(string name, string emailAddress);

  public void GetResultsOnCallback(IAsyncResult ar)
  {
   SendEmailDelegate del = (SendEmailDelegate)
    ((AsyncResult)ar).AsyncDelegate;
   try
   {
    string result;
    result = del.EndInvoke(ar);
    Debug.WriteLine("\nOn CallBack: result is " + result);
   }
   catch (Exception ex)
   {
    Debug.WriteLine("\nOn CallBack, problem occurred: " + ex.Message);   }
  }

  public string SendEmailAsync(string name,string emailAddress)
  {
   SendEmailDelegate dc = new SendEmailDelegate(this.SendEmail); 
   AsyncCallback cb = new AsyncCallback(this.GetResultsOnCallback);
   IAsyncResult ar = dc.BeginInvoke(name,emailAddress, cb, null); 
   return "ok";
  }
 } // end class EmailUtil 
}

So we start out with some handy properties (SmtpServer, etc.) which can be set on the class prior to calling any methods, for convenience purposes. Then we have our SendEmail method, which looks like any normal synchronous method - no difference at all.

Now comes the interesting stuff: First, we need a delegate:

public delegate string SendEmailDelegate(string name, string emailAddress);

Then finally we need the callback method:

public void GetResultsOnCallback(IAsyncResult ar)
{
SendEmailDelegate del = (SendEmailDelegate) ((AsyncResult)ar).AsyncDelegate;
string result;
result = del.EndInvoke(ar);
}

Notice the delegate must have the same signature as the method. Based on this delegate syntax, we can now call the BeginInvoke() and EndInvoke() methods:

public string SendEmailAsync(string name,string emailAddress)
{
SendEmailDelegate dc = new SendEmailDelegate(this.SendEmail);
AsyncCallback cb = new AsyncCallback(this.GetResultsOnCallback);
IAsyncResult ar = dc.BeginInvoke(name,emailAddress, cb, null);
return
"ok";
}

Note that the delegate calls BeginInvoke / EndInvoke use a Framework – managed ThreadPool under the hood--
so you don’t get 4,000 runaway threads. When a thread completes, it is automatically made available to be re-used for the next BeginInvoke call in your loop. So your default ThreadPool size of 25 threads is as high as you get. You could experminent with different ThreadPool sizes, but the main thing is you want to avoid just creating thousands of threads, one for each email, and having your box crash under the load.

To use this class, for example, with a DataSet containing a table of names and email addresses of our subscribes, we might use code like the following:

DataTable dt = ds.Tables[0];
EmailUtil eu = new EmailUtil();
eu.HtmlFilePath=@"c:\temp\file.htm";
eu.Subject ="Newsletter for you!";
eu.SmtpServer="mail.myserver.com";
eu.FromEmail="you@yourserver.com";
for (int i =0; i<dt.Rows.count;i++)
{
eu.SendEmailAsync(dt.Rows[0].Items["name"].ToString(), dt.Rows[0].Items["email"].ToString());
}

The really cool thing about this exercise is that you can use the technique above to wire up almost any class / method that doesn't explicitly expose an asynchronous version, to be successfully called asynchronously. Use with caution, please don't send out spam, folks.

 

Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here.  --><--NOTE: Post QUESTIONS on FORUMS!

Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.