I’m currently working on a Question/Answer website and users are notified of new answers to a question they’ve subscribed. Similarly emails are sent out when there are new comments to an article for which you’ve subscribed. The other types of emailing present in the system is when a user registers for the first time and an activation email is sent out to them or when they send a request to reset their password through a “Forgotten Password” link or when the administrator wants to send out an email to every member (kind of a broadcast or newsletter thing).
To me, the registration and password reset email need to be sent straight away for a good user experience. So this can be done synchronously as soon as the registration form is completed or the password reset button is clicked. However when a user adds a new answer/comment, you cannot have the same worker process handle the notification, that is, send emails to subscribed users to tell them there’s a new answer/comment. This will be bad experience for the user who submitted the answer/comment because the request will be blocked until the emailing is done and if you have to notify 100 users, this can take a really long time.
There are a few things which can be done though such as getting the email sending to be done asynchronously, relaying the emails from IIS SMTP to the mail server or using MSMQ (Microsft Messaging Queue) to queue up the emails.
Sending emails asynchronously in C#
With .net 4.o framework, it has become even more easy to send emails asynchronously. You will need to have the Async property to true in the Page directive and use SendAsync instead of Send on the smtp object (you need to hook up the delegates as well but I’m not going into that for the time being).
What happens then is that the worker process which served the http request will be returned to the application pool once the async method is hit and another thread will be processing the asynchronously event. Once it is complete, the response is then sent to the user. The advantage of doing this is that you free up worker processes to serve other http requests instead of starving the thread pool. When your async job is finished, another worker process will be grabbed to finish off the request which had the async method.
The main disadvantage of the async page is that the user who initiated the request will have to wait until the async event completes before he sees any response. If you’re doing heavy work (as in emailing 100 people), then the user can get really frustrated and leave and that’s not very good for user experience. Http requests need to be quick and process intensive tasks need to be done by a background thread.
What we really need is a fire and forget method and that can be achieved with a background thread (see this link http://www.jdconley.com/blog/archive/2009/01/14/fire-and-forget-email-webservices-and-more-in-asp.net.aspx). Context switching is an expensive task though and the problem with this approach is that the thread will go into unmanaged code, so you will need to be careful what it’s doing.
IIS SMTP relay to remote mail server
Using the System.Net.Mail library is great but communicating through the SMTP protocol is an expensive task. First a connection has to be established with the remote mail server and then the destination email address needs to be checked and if it exists, then the delivery of the email can be done. Now imagine doing that for a large amount of emails. It will take a really long time. Therefore it is much easier to use PickupDirectoryFromIis or SpecifiedPickupDirectory for the SmtpDeliveryMethod.
If you use Network delivery, then the email will be sent through the smtp protocol but if you use PickupDirectoryFromIis, then the email is saved as a file in the \inetpub\mailroot\pickup folder with the .eml extension. Email files are easier to process and since you’re not connecting to the remote mail server and simply saving the emails as files, you will find it much quicker to process.
Once in the pickup folder though, you will need a certain process to deliver the emails. You can get IIS Smtp to deliver the emails directly or have it relay to your remote mail server. The latter is the better approach since your mail server can do the spam checking and authentication and will not be blacklisted by other email servers.
IIS SMTP on Windows 7 and Windows 2008
It is important to note that Windows 7 does not come with a version of the IIS SMTP server. With Windows 2008 though, you can get the SMTP installed by going into Features and adding it but it runs in IIS6 mode and not IIS7 or IIS7.5. Therefore you will need to have IIS6 running on your server even if you use IIS7/7.5 for your websites.
The IIS SMTP service should be used as an email relay in the sense that email dropped in the pickup folder are forwarded to the actual email server (smtp) for processing.
Using Microsoft Messaging Queue (MSMQ) to notify users through email
A queuing system is a good way to send email as MSMQ can help with that. Basically, instead of sending the emails directly to the mail server through the network, you save the messages in a queue with MSMQ. They will stay in the folder they’re saved and another process (eg a windows service) can read that folder and process the emails (deliver them). The main advantage to this is that you do not overwork your CPU as in 100% at peak times and 0% during non-peak hours but rather use the CPU in a more efficient manner because the work is queued up and can be processed when resources becomes available.
The following link is a good start (http://dotnetslackers.com/articles/aspnet/Sending-email-from-ASP-NET-MVC-through-MVC-and-MSMQ-Part2.aspx).
Conclusion
The mechanism behind sending mass emails to users who have subscribed to a particular service is very important because you do not want your users to wait a long time because there’s a slow process which has been triggered by some action they’ve taken on your website. For the user, things must be quick and it is therefore important to have background tasks run on a different thread than the calling thread.