Where to store email templates
I have an asp.net web application, which sends several emails to users during the signup procedure. Right now I have them inline with the code, but I would like to hold th开发者_如何学Cem in a central location where I can edit them without going in to VS.
What would be the best place/format to store these HTML templates?
I store all my e-mail templates for my web-app as ASP.NET MVC Razor Views, but as embedded resource in a light assembly that I can easily reference from any project.
A template looks like this (notice the localization):
@model Milkshake.Commerce.Model.Users.UserDto
@using Milkshake.Core.Internationalization;
@using Milkshake.Commerce.Model.Meta;
@if (Language.CurrentForInterface.TwoLetterISOLanguageName.Equals("da"))
{
<h1>Hej @Model.FirstName</h1>
<p>
Din nye brugerkonto til Milkshake Commerce er blevet oprettet.
</p>
<p>
Gå til dine <a href="http://@ShopSettings.Instance.Domain.TrimEnd('/')/Account">konto indstillinger</a>, brug din e-mail adresse som adgangskode og du vil blive videreført til dine konto indstillinger, hvor du kan ændre din adgangskode.
</p>
<p>Ha' en god dag!</p>
<h2>The Milkshake Commerce Team!</h2>
}
else
{
<h1>Hi @Model.FirstName</h1>
<p>
Your new user account for Milkshake Commerce has been created for you.
</p>
<p>
Go to your <a href="http://@ShopSettings.Instance.Domain.TrimEnd('/')/Account">user account page</a>, use your e-mail address as password and you'll be taken directly to your account page where you can change your password.
</p>
<p>Have a nice day!</p>
<h2>The Milkshake Commerce Team!</h2>
}
Then I have a "master" template, called _AppEmailTemplate.cshtml
:
@using Milkshake.Commerce.Model.Resources
<!DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style type="text/css">
body
{
font-family: Arial, Helvetica;
}
.layout-wrapper
{
width: 600px;
}
.header
{
background-color: #242225;
}
.header img
{
display: block;
}
.content
{
background-color: #ffffff; padding: 10px 20px; border: 10px solid #eaeaea; border-top: none;
}
.footer
{
padding: 20px; padding-top: 5px; font-size: 10px; color: #cccccc;
}
p
{
font-size: 14px;
}
p.company-details
{
font-size: 12px;
}
h1
{
font-size: 20px;
}
h2
{
font-size: 16px;
}
</style>
<style type="text/css" id="mobile">
@@media only screen and (max-device-width: 480px) {
body
{
}
.layout-wrapper
{
width: 480px !important;
}
.header
{
background-color: transparent !important;
}
.header img
{
width: 480px !important;
}
.content
{
border: none !important;
}
.footer
{
padding-top: 15px !important;
}
p
{
font-size: 22px !important;
}
h1
{
font-size: 28px !important;
}
h2
{
font-size: 24px !important;
}
}
</style>
</head>
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0" bgcolor="#f1f1f1">
<table width="100%" cellpadding="0" cellspacing="0" bgcolor="#f1f1f1">
<tr>
<td valign="top" align="center">
<table cellpadding="0" cellspacing="0" width="100%" height="80">
<tr>
<td class="header" align="center">
<table cellpadding="0" cellspacing="0" width="600" height="80" class="layout-wrapper" style="width: 600px;">
<tr>
<td>
<img src="http://example.com/email-header.png" alt="Milkshake Commerce" />
</td>
</tr>
</table>
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" width="600" class="layout-wrapper">
<tr>
<td class="content" align="left">
#¤#¤CONTENTSECTION#¤#¤
</td>
</tr>
<tr>
<td class="footer" align="left">
<p>@Text.appEmailDisclaimer</p>
<p>@Text.appEmailFooterAd.UrlDecode()</p>
<p class="company-details"><i>Company name etc.</i></p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
To actually send the e-mail, I use RazorEngine for rendering:
public void SendSystemEmail<T>(string templateName, string subject, string fromName, string recipientEmail, T model)
{
dynamic template = this.GetEmailTemplate(templateName);
string layoutBody = RazorEngine.Razor.Parse(template.Layout as string, model);
string emailBody = RazorEngine.Razor.Parse(template.Template as string, model);
emailBody = layoutBody.Replace(CONTENTSECTIONREPLACETOKEN, emailBody);
PreMailer.Net.PreMailer pm = new PreMailer.Net.PreMailer();
emailBody = pm.MoveCssInline(emailBody, true);
EmailDto email = new EmailDto();
email.Body = emailBody;
email.IsBodyHtml = true;
email.FromEmail = "support@example.com";
email.ReplyToEmail = email.FromEmail;
email.FromName = fromName;
email.RecipientEmail = recipientEmail;
email.Subject = subject;
email.Type = EmailTypes.Transactional;
if (String.IsNullOrWhiteSpace(email.FromName))
{
email.FromName = "Milkshake Software";
}
this.SendMailMessages(new List<EmailDto>() { email }, false);
}
The above code uses my own EmailDto object. Here you can easily create a [MailMessage][2]
instance directly, and send it using the [SmtpClient][3]
.
Also, to get the best rendering in all e-mail clients, I use my own PreMailer.Net library to move all CSS inline. Read my blog post here, for more information. (Code is on Github)
The GetEmailTemplate does this:
/// <summary>
/// Gets the email template.
/// </summary>
/// <param name="templateName">Name of the template.</param>
/// <returns>Returns the e-mail template.</returns>
private dynamic GetEmailTemplate(string templateName)
{
string masterTemplateContents = this.GetTemplateFileContents("_AppEmailTemplate.cshtml");
string templateContents = this.GetTemplateFileContents(templateName + ".html.cshtml");
return new { Layout = masterTemplateContents, Template = templateContents };
}
/// <summary>
/// Gets the template file contents.
/// </summary>
/// <param name="templateFileName">The name of the template file.</param>
/// <returns>Returns the contents of the template file.</returns>
private string GetTemplateFileContents(string templateFileName)
{
return this.GetEmailFileContents("Templates", templateFileName);
}
/// <summary>
/// Gets the email file contents.
/// </summary>
/// <param name="lastNamespaceToken">The last namespace token.</param>
/// <param name="templateFileName">The name of the template file.</param>
/// <returns>
/// Returns the contents of the template file.
/// </returns>
private string GetEmailFileContents(string lastNamespaceToken, string templateFileName)
{
var assembly = Assembly.GetExecutingAssembly();
if (assembly != null)
{
StringBuilder sb = new StringBuilder();
using (StreamReader sr = new StreamReader(assembly.GetManifestResourceStream(String.Format("MyApp.BusinessLogic.Communication.{0}.{1}", lastNamespaceToken, templateFileName))))
{
while (!sr.EndOfStream)
{
var line = sr.ReadLine();
if (!line.StartsWith("@model"))
{
sb.AppendLine(line);
}
}
}
return sb.ToString();
}
return null;
}
That depends on how often the templates will change and who they will be changed by. E.g:
Changed by users of the application, changes are urgent and potentially frequent:
- Probably best stored in the database and loaded each time an e-mail is sent.
Changed by developers (i.e. you), changes are infrequent and not urgen:
- text files on the webserver, load them into cache and store them there, only reloading them when the cache drops or the application restarts.
I would recommend storing the e-mail templates in an XML file which will allow scalability in the future by adding attributes to the mail template and it will also allow easy edit.
Hope you were sorted, this is what I found and thought was easier.
- Open properties of your project.
- Go to the resource. Under resource 'Add Resource' => 'Add Existing File'
Then you can just access it like this.
var html = Resources.YourFileName;
Remember to add using
using YourProject.Properties;
Thank you all for giving an insights about how they are handling. I have gathered lots of knowledge from here. I liked @MartinHN using Razor parser with concrete Model of Data.
However, something it did not worked very well for me.
Requirement:
I have to store email templates such that I can display the same to
Stakeholders anytime. Thus it should available to browse through Intranet - preferably through sae website as hosted API.The front end designers should be able to modify templates easily. Thus I want to store it in plain HTML format so that designer does not have to go through on too much of technical details.
The email templates should be easily available for modifications for Administrators (future requirement). In near future, there will be different notifications for SMS, Screen. Thus the templates are different.
Based on these requirements, I did the following:
Since I was using MVC, I created a folder called "STATIC" that is available to be browsed directly (and MVC engine/ http handler exclude this folder from performing its MVC activities).
With this approach I could easily achieve first requirement and I could send my link to the stake holders as http://api.aksdfjl.com/static/welcomeemailtemplate.html
Each email template has given its own html, thus it was easy for the designer to access the same and refer it on their repository as a shortcut to my repository folder. The Html has inline CSS and it is completely an independent html - per Email.
The last main requirement was about maintaining these design and user can modify the same. Well then definitely I don't want to deal through file system at all. What I did is now these notifications stores into the database and I initialize them once. After that the Admin panel has got a wysiwyg html editor that can give them a quick preview as well as control on what it should send to.
Now I wanted to make sure that future requirements are easily handled and since my company was introducing different notifications for different mode such as E-mail, Screen, SMS notifications. I decided to extend the software design with the help of template initializer XML that store these answers.
The mother of all template called - MessageTemplates.xml stores different information that I need to initialize different type of templates i.e. email ,sms, screen et al.
Here is how the code looks like now.
[HttpGet]
[Route("applications/initializenotificationtemplate")]
public IHttpActionResult InitializeNotificationTemplate()
{
return
InitializeNotificationTemplate(Path.Combine(HostingEnvironment.ApplicationPhysicalPath,
@"Static\InitializeData\MessageTemplates.xml"));
}
[NonAction]
public IHttpActionResult InitializeMailTemplate(string filePath)
{
try
{
_applicationService.InitializeTemplate(filePath);
return Ok("Application Notification templates are initialized.");
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
_applicationService.InitializeTemplate has the following definition:
public bool InitializeTemplate(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentNullException("File Path");
}
if (!File.Exists(filePath))
{
throw new FileNotFoundException(filePath);
}
var data = _notificationTemplateService.Get();
var exceptionMessages = string.Empty;
if (data != null)
{
var historicalTemplates = data.ToList();
historicalTemplates.ForEach((d) => _notificationTemplateService.Delete(d, out exceptionMessages));
}
XDocument xmlDocument = XDocument.Load(filePath);
IEnumerable<NotificationTemplate> templates = (from template in xmlDocument.Descendants("Template")
select new NotificationTemplate()
{
Title = template.Element("Subject").Value,
Description = template.Element("Body").Value,
Type = (NotificationTypeOptions)Enum.Parse(typeof(NotificationTypeOptions), template.Element("Type").Value, true),
Category = (NotificationCategoryOptions)Enum.Parse(typeof(NotificationCategoryOptions), template.Attribute("category").Value, true),
}).ToList();
foreach (var t in templates)
{
var path = Path.Combine(Path.GetDirectoryName(filePath), Regex.Replace(t.Description, @"\t|\n|\r| ", ""));
if (File.Exists(path))
{
StreamReader reader = new StreamReader(path);
t.Description = reader.ReadToEnd();
}
else
{
t.Description = string.Empty;
}
}
return _notificationTemplateService.InsertRange(templates, out exceptionMessages);
}
This is how my model looks like that is same as database model (code first - EF approach).
public class NotificationTemplate : IdentityBase
{
public string Category { get; set; }
public NotificationTypeOptions Type { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public NotificationTemplate()
{
Type = NotificationTypeOptions.Email;
}
}
[Flags]
public enum NotificationTypeOptions
{
Email = 0,
Screen = 1,
}
For the very first time, when I install my application I call initialize API call that install my notification templates into the database and all other options are available and ready to use.
Now with this approach I made everyone happy in the organisation and it has a great strength to extend this further so that it is easy for me to introduce a new templates as well.
You can store Email Templates in an .html file
. Then format it in way that will support the parameter's you want to include. e.g.
<head>
<title></title>
</head>
<body>
Hello <!--Name--> ,
This is a test template
User Name: <!--UserName-->
.............................
.............................
</body>
</html>
Whenever you send an email to the user, you want to set the template as user specific, so you can replace the parameter at runtime.
精彩评论