Monday, 29 June 2015

Sending emails from a background thread in asp.net mvc


Introduction
In the current application am working on I encountered a problem, and that is how to send an email from the backend. This problem was unique as there are currently no nuget project tackling this problem, because the available asp.net mvc email sending packages (like Mvc Mailer, Postal) can only send mail in the context of a request. In this post we are going to look at how to send mails in the background while making effective use of views to order and style your mail.

In this project, we are going to need 2 essential nuget packages: RazorEngine, PreMailer. So fire up your visual studio, create an asp.net mvc project and add the following package in nuget manager console.

Install-Package RazorEngine

This nuget package allows you to use razor to build robust template. Since we only want to be sending professional looking emails, we are going to use it in creating our message body so as to enable to style it appropriately instead of using string concatenation or ordinary text.

Install-Package Premailer.net

This nuget package will be used in converting page head style blocks to inline styles as most email clients dont render perfectly if you don't inline its css.

In this project, we are going to utilize Quartz, to schedule that our email to sent 5 minutes after it was created. So add Quartz also to your project

Install-Package Quartz




The next thing to do is to create a folder where your mail templates will live. Its not a must for you to create the folder inside the Views Directory but its advisable to create the folder where you can easily access it from the root of the AppDomain.

After that create a base mail layout inside the 'mails template' folder. This will serve as the layout for your emails, as it will help in the styling of the emails like the one shown below:


1:  @model dynamic  
2:  @using System.Web.Optimization  
3:  <!DOCTYPE html>  
4:  <html>  
5:  <head>  
6:    <style rel="stylesheet">  
7:      body {  
8:        background-color: #bbb;  
9:        font-family: arial helvetica sans-serif;  
10:        font-size: 16px;  
11:        line-height: 1.557;  
12:        margin: 0;  
13:        padding: 0;  
14:      }  
15:      .mailBody {  
16:        margin: 15px auto;  
17:        width: 750px;  
18:        padding: 0 10px 10px;  
19:        background-color: #fff;  
20:        -ms-border-radius: 5px;  
21:        -moz-border-radius: 5px;  
22:        -webkit-border-radius: 5px;  
23:        border-radius: 5px;  
24:      }  
25:      .header {  
26:        background-color: #2478BF;  
27:        margin: 0;  
28:        padding: 10px;  
29:      }  
30:      .logo {  
31:        text-align: center;  
32:        color: #fff;  
33:      }  
34:      .col-md-8 {  
35:        position: relative;  
36:        min-height: 1px;  
37:        padding-right: 15px;  
38:        padding-left: 15px;  
39:      }  
40:      .btn {  
41:        padding: 10px 15px;  
42:        display: block;  
43:        background-color: #2478BF;  
44:        color: #fff;  
45:        text-decoration: none;  
46:        text-align: center;  
47:        -ms-border-radius: 5px;  
48:        -moz-border-radius: 5px;  
49:        -webkit-border-radius: 5px;  
50:        border-radius: 5px;  
51:        border: 1px solid #206cac;  
52:      }  
53:      .col-md-offset-2 {  
54:        margin-left: 16.66666667%;  
55:      }  
56:      .container {  
57:        width: 750px;  
58:        margin: 0 auto;  
59:      }  
60:      .center {  
61:        width: 350px;  
62:        margin: 10px auto;  
63:      }  
64:      .clearfix:before,  
65:      .clearfix:after {  
66:        display: table;  
67:        content: " ";  
68:      }  
69:      .clearfix:after {  
70:        clear: both;  
71:      }  
72:      .float-right {  
73:        float: right;  
74:      }  
75:    </style>  
76:  </head>  
77:  <body class="container" style="background-color: #bbb;">  
78:    <div class="mailBody">  
79:      <header class="header">  
80:        <h3 class="logo">Welcome to <a href="http://oursite.com">Our site</a></h3>  
81:      </header>  
82:      <div>  
83:        @RenderBody()  
84:      </div>  
85:      <div class="clearfix">  
86:        <hgroup class="float-right">  
87:          <h5>Yours sincerely</h5>  
88:          <span>Kings, Admin</span>  
89:        </hgroup>  
90:      </div>  
91:    </div>  
92:  </body>  
93:  </html>  


The next thing to do is to go ahead and create view templates that will be used by the email sender to compile your emails.

1:  @using BackgroundMailSender.Common.MailWorker  
2:  @model Message  
3:  @{  
4:    Layout = "_MailLayout.cshtml";  
5:  }  
6:  <div>  
7:    @Message.Body  
8:  </div>  

Then lets create classes that we be using to pass message to our background sender. We will call the class Message.cs


1:  namespace BackgroundMailSender.Common.MailWorker  
2:  {  
3:    public class Message  
4:    {  
5:      public string Fullname { get; set; }  
6:      public string Body { get; set; }  
7:      public string Title { get; set; }  
8:      public string To { get; set; }  
9:    }  
10:  }  

//Here is the email class

 public class Email
    {
        public Email(string to)
        {
            if (!CommonHelper.IsValidEmail(to))
                throw new ArgumentNullException(to, "This is not a valid email address!");
            Priority = MailPriority.Normal;
            To = to;
        }
        public string To { get; private set; }
        public string Message { get; set; }
        public MailPriority Priority { get; set; }
        public string Title { get; set; }
    }
}


Here is our EmailWorker class.

1:  using System;  
2:  using System.IO;  
3:  using System.Net;  
4:  using System.Net.Mail;  
5:  using System.Threading.Tasks;  
6:  using System.Web.Hosting;  
7:  using MailEngine.MailWorker;  
8:  using RazorEngine;  
9:  using RazorEngine.Templating;;  
10:    
11:  namespace BackgroundMailSender.Common.MailWorker  
12:  {  
13:    public class EmailWorker  
14:    {  
15:      private const string EmailTemplatePath = "~/Views/EmailTemplates";  
16:      private string _templatePath = string.Empty;  
17:    
18:      public EmailWorker()  
19:      {  
20:        //Resolve virtual url to file path url  
21:        _templatePath = MapPath(EmailTemplatePath);  
22:    
23:        //Map the Layout path.  
24:        var mailLayoutPath = Path.Combine(_templatePath, "_MailLayout.cshtml");  
25:    
26:    
27:        //Add out layout template.  
28:        var layout = File.ReadAllText(mailLayoutPath);  
29:        Engine.Razor.AddTemplate("_MailLayout", layout);  
30:    
31:    
32:      }  
33:    
34:      /// <summary>  
35:      ///   Maps a virtual path to a physical disk path.  
36:      /// </summary>  
37:      /// <param name="path">The path to map. E.g. "~/bin"</param>  
38:      /// <returns>The physical path. E.g. "c:\inetpub\wwwroot\bin"</returns>  
39:      private string MapPath(string path)  
40:      {  
41:        if (HostingEnvironment.IsHosted)  
42:        {  
43:          //hosted  
44:          return HostingEnvironment.MapPath(path);  
45:        }  
46:        //not hosted. For example, run in unit tests  
47:        string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;  
48:        path = path.Replace("~/", "").TrimStart('/').Replace('/', '\\');  
49:        return Path.Combine(baseDirectory, path);  
50:      }  
51:    
52:      private Task Send(Email email)  
53:      {  
54:        Func<Task> funct = () =>  
55:        {  
56:          var message = new MailMessage  
57:          {  
58:            Body = email.Body,  
59:            IsBodyHtml = true,  
60:            Subject = email.Subject,  
61:            Priority = MailPriority.Normal  
62:          };  
63:          message.To.Add(email.To);  
64:    
65:          var smtp = new SmtpClient();  
66:          return smtp.SendMailAsync(message);  
67:        };  
68:    
69:        var task = Task.Run(funct);  
70:        return task;  
71:      }  
72:    
73:      /// <summary>  
74:      /// converts block level css to inline css  
75:      /// </summary>  
76:      /// <param name="razorResult"></param>  
77:      /// <returns>Inlined html</returns>  
78:      private string InlineCss(string razorResult)  
79:      {  
80:        //Create a new Premailer instance, note this can be done using Dependency injection.  
81:        var pm = new PreMailer.Net.PreMailer(razorResult);  
82:    
83:        //Inline css  
84:        var compileResult = pm.MoveCssInline(stripIdAndClassAttributes: true,  
85:          removeComments: true, removeStyleElements: true);  
86:    
87:        return compileResult.Html;  
88:      }  
89:    
90:    
91:      public Task SendMessage(Message mail)  
92:      {  
93:        var task = Task.Run(() =>  
94:        {  
95:          if (mail == null)  
96:            return Task.FromResult(0);  
97:    
98:          string commonTemplates = Path.Combine(_templatePath, "Message.cshtml");  
99:          string cshtml = File.ReadAllText(commonTemplates);  
100:    
101:          //Add a new message template.  
102:          Engine.Razor.AddTemplate("MessageTemplate", cshtml);  
103:    
104:          string result = Engine.Razor.RunCompile("CommonTemplates", typeof (Message),  
105:            mail);  
106:    
107:          //Use Premailer.net to inline css  
108:          var inlineResult = InlineCss(result);  
109:    
110:          var email = new Email  
111:          {  
112:            To = mail.To,  
113:            Body = inlineResult,  
114:            Subject = mail.Title  
115:          };  
116:    
117:          return Send(email);  
118:        });  
119:        return task;  
120:      }  
121:    }  
122:  }  

Notice that in the constructor that the main mail layout was added. This class has 2 major private methods: InlineCss() and Send(). 

The InlineCss method inlines the block level styles in the mail layout into the html 'styles' tag using the Premailer.net which we added in the beginning. While the SendMessage sends the message using the good old 'System.Net.Mail'.

Another class method worth pointing out at is the SendMessage() public method, this method takes in the Message you are sending, compiles it with the prescribed template calls the InlineCss method and then sends out the message by calling the Send() private method. This method serves as the controller  in the mailer service.

Finally add the system.net.mail configuration in your config file.


1:   <system.net>  
2:       <mailSettings>  
3:        <!--Specify a pick up directory for testing-->  
4:        <smtp from="Kings &lt;admin@oursite.com&gt;" deliveryMethod="SpecifiedPickupDirectory">  
5:         <network host="localhost" />  
6:         <specifiedPickupDirectory pickupDirectoryLocation="c:\users\...\documents\emails" />  
7:        </smtp>  
8:       </mailSettings>  
9:     </system.net>  
10:    

There you have it a background email sender that you can use in any project, be it console, winforms, wpf or asp.net project. Hope it helps someone. Here is the full source code in github. Here is the email I sent using the background sender



Feel free and download and play with source code

2 comments:

  1. Thanks , for this article .
    I want to ask where is this 'Email' Class which you have used in 'SendMessage' and
    'Send' Method .

    ReplyDelete
  2. Sorry for not responding long ago, I have edited the article and added the email class. The class is just a data class used for storing required values.

    ReplyDelete