126

Is there a better way to generate HTML email in C# (for sending via System.Net.Mail), than using a Stringbuilder to do the following:

string userName = "John Doe";
StringBuilder mailBody = new StringBuilder();
mailBody.AppendFormat("<h1>Heading Here</h1>");
mailBody.AppendFormat("Dear {0}," userName);
mailBody.AppendFormat("<br />");
mailBody.AppendFormat("<p>First part of the email body goes here</p>");

and so on, and so forth?

Rob
  • 45,296
  • 24
  • 122
  • 150
  • Well, it really depends on the solution as I see it. I have done everything from grabbing user-input and formatting it automaticly from different patters. The best solution I've done with html mails was actually xml+xslt formatting since we knew the input of the mail up-front. – cyberzed May 20 '09 at 08:14
  • That depends how complex your requirements are. I once had an application that rendered a table in an HTML email and I used an ASP.NET Gridview to render the HTML- concatenating strings to generate a table would have been messy. – RichardOD May 20 '09 at 08:15

10 Answers10

189

You can use the MailDefinition class.

This is how you use it:

MailDefinition md = new MailDefinition();
md.From = "test@domain.example";
md.IsBodyHtml = true;
md.Subject = "Test of MailDefinition";

ListDictionary replacements = new ListDictionary();
replacements.Add("{name}", "Martin");
replacements.Add("{country}", "Denmark");

string body = "<div>Hello {name} You're from {country}.</div>";

MailMessage msg = md.CreateMailMessage("you@anywhere.example", replacements, body, new System.Web.UI.Control());

Also, I've written a blog post on how to generate HTML e-mail body in C# using templates using the MailDefinition class.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
MartinHN
  • 19,542
  • 19
  • 89
  • 131
  • 6
    +1. Nice, although limited, probably covers many uses. Not so useful if you want to programmatically include sections of HTML and/or loop through a set of items that need rendering. – AnthonyWJones May 20 '09 at 09:25
  • I became aware of it just recently. It is cool. I guess it tells you how important it is to look at the MSDN Documentation before writing a class for any problem yourself. I had written my own class, that did almost the same as MailDefinition. Too bad for me. Waste of time. – MartinHN May 20 '09 at 12:48
  • 4
    I was uncomfortable using the MailDefinition class because of the limited options for specifying the from, to, cc, and bcc fields. It also relies on a namespace for Web UI controls--that doesn't make sense to me. See my answer below... – Seth Dec 14 '12 at 00:10
  • 1
    +1 as I did not know this class existed, although the underlying implementation is simply iterating over the replacements list and running `Regex.Replace(body, pattern, replacement, RegexOptions.IgnoreCase);` for each key/value pair ... in light of that detail, this class doesn't provide much value as used above unless working with an existing reference to `System.Web.UI.Controls` . – Chris Baxter Mar 11 '16 at 20:52
  • How is this better than using `@$"
    {someValue}
    "`? I'd actually think your code is less readable than dealing directly with strings
    – Kellen Stuart Oct 08 '19 at 15:33
31

Use the System.Web.UI.HtmlTextWriter class.

StringWriter writer = new StringWriter();
HtmlTextWriter html = new HtmlTextWriter(writer);

html.RenderBeginTag(HtmlTextWriterTag.H1);
html.WriteEncodedText("Heading Here");
html.RenderEndTag();
html.WriteEncodedText(String.Format("Dear {0}", userName));
html.WriteBreak();
html.RenderBeginTag(HtmlTextWriterTag.P);
html.WriteEncodedText("First part of the email body goes here");
html.RenderEndTag();
html.Flush();

string htmlString = writer.ToString();

For extensive HTML that includes the creation of style attributes HtmlTextWriter is probably the best way to go. However it can be a bit clunky to use and some developers like the markup itself to be easily read but perversly HtmlTextWriter's choices with regard indentation is a bit wierd.

In this example you can also use XmlTextWriter quite effectively:-

writer = new StringWriter();
XmlTextWriter xml = new XmlTextWriter(writer);
xml.Formatting = Formatting.Indented;
xml.WriteElementString("h1", "Heading Here");
xml.WriteString(String.Format("Dear {0}", userName));
xml.WriteStartElement("br");
xml.WriteEndElement();
xml.WriteElementString("p", "First part of the email body goes here");
xml.Flush();
AnthonyWJones
  • 187,081
  • 35
  • 232
  • 306
  • Hi, how would one add inline styling to for example the `h1` tag? – Izzy Sep 22 '17 at 08:18
  • when the email pop up was opened,the body,iam using div tag in my context it was rendering as <25>.can u please help with last step how to make a proper rendering – clarifier Feb 26 '18 at 13:54
18

Updated Answer:

The documentation for SmtpClient, the class used in this answer, now reads, 'Obsolete("SmtpClient and its network of types are poorly designed, we strongly recommend you use https://github.com/jstedfast/MailKit and https://github.com/jstedfast/MimeKit instead")'.

Source: https://www.infoq.com/news/2017/04/MailKit-MimeKit-Official

Original Answer:

Using the MailDefinition class is the wrong approach. Yes, it's handy, but it's also primitive and depends on web UI controls--that doesn't make sense for something that is typically a server-side task.

The approach presented below is based on MSDN documentation and Qureshi's post on CodeProject.com.

NOTE: This example extracts the HTML file, images, and attachments from embedded resources, but using other alternatives to get streams for these elements are fine, e.g. hard-coded strings, local files, and so on.

Stream htmlStream = null;
Stream imageStream = null;
Stream fileStream = null;
try
{
    // Create the message.
    var from = new MailAddress(FROM_EMAIL, FROM_NAME);
    var to = new MailAddress(TO_EMAIL, TO_NAME);
    var msg = new MailMessage(from, to);
    msg.Subject = SUBJECT;
    msg.SubjectEncoding = Encoding.UTF8;
 
    // Get the HTML from an embedded resource.
    var assembly = Assembly.GetExecutingAssembly();
    htmlStream = assembly.GetManifestResourceStream(HTML_RESOURCE_PATH);
 
    // Perform replacements on the HTML file (if you're using it as a template).
    var reader = new StreamReader(htmlStream);
    var body = reader
        .ReadToEnd()
        .Replace("%TEMPLATE_TOKEN1%", TOKEN1_VALUE)
        .Replace("%TEMPLATE_TOKEN2%", TOKEN2_VALUE); // and so on...
 
    // Create an alternate view and add it to the email.
    var altView = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
    msg.AlternateViews.Add(altView);
 
    // Get the image from an embedded resource. The <img> tag in the HTML is:
    //     <img src="pid:IMAGE.PNG">
    imageStream = assembly.GetManifestResourceStream(IMAGE_RESOURCE_PATH);
    var linkedImage = new LinkedResource(imageStream, "image/png");
    linkedImage.ContentId = "IMAGE.PNG";
    altView.LinkedResources.Add(linkedImage);
 
    // Get the attachment from an embedded resource.
    fileStream = assembly.GetManifestResourceStream(FILE_RESOURCE_PATH);
    var file = new Attachment(fileStream, MediaTypeNames.Application.Pdf);
    file.Name = "FILE.PDF";
    msg.Attachments.Add(file);
 
    // Send the email
    var client = new SmtpClient(...);
    client.Credentials = new NetworkCredential(...);
    client.Send(msg);
}
finally
{
    if (fileStream != null) fileStream.Dispose();
    if (imageStream != null) imageStream.Dispose();
    if (htmlStream != null) htmlStream.Dispose();
}
Seth
  • 6,514
  • 5
  • 49
  • 58
  • 1
    FWIW this code has been tested and is being used in a production application. – Seth Apr 30 '13 at 22:36
  • This does not look right, attaching the HTML with mediatype PDF? var file = new Attachment(fileStream, MediaTypeNames.Application.Pdf); – Gudlaugur Egilsson Jan 14 '14 at 13:09
  • The portion of the sample you're referring to demonstrates how to attach a PDF to the email. – Seth Jan 14 '14 at 17:28
  • 1
    This is a bit confusing, since in some other libraries, HTML is sent as an attachment. I suggest removing the PDF attachment portion from this sample to make it clearer. Furthermore, the msg.Body is never set in this sample code, I assume it should be assigned the body variable? – Gudlaugur Egilsson Jan 16 '14 at 09:49
  • 1
    A key step is missing, which setting msg.IsBodyHtml=true. See this answer here: http://stackoverflow.com/questions/7873155/mailmessage-c-sharp-how-to-make-it-html-and-add-images-etc – Gudlaugur Egilsson Jan 16 '14 at 10:25
  • That's a weird way of getting at resources. Also, why are you explicitly calling `Dispose()`? Why not just `using` blocks? While I'm convinced this *works* it looks like a nightmare if you'd have to modify it. Maybe just coding style... I think I'd rather write mail templates using something called `MailDefinition`, even with the unfortunate dependency to web controls. – Yuck Feb 06 '14 at 03:40
  • Yes you could do a three line using statement to clean it up a little, see http://stackoverflow.com/questions/1329739/nested-using-statements-in-c-sharp. I put this into a single snippet for the sake of StackOverflow; you should be able to factor it out pretty easily. Cheers. – Seth Feb 06 '14 at 22:22
  • any sample for HTML_RESOURCE_PATH ? – Kiquenet Jul 19 '18 at 07:11
  • @Kiquenet see the usage for GetManifestResourceStream: https://stackoverflow.com/questions/15276969/what-is-the-resource-name-for-a-manifest-resource-assembly-getmanifestresources – Seth Jul 21 '18 at 14:51
7

I use dotLiquid for exactly this task.

It takes a template, and fills special identifiers with the content of an anonymous object.

//define template
String templateSource = "<h1>{{Heading}}</h1>Dear {{UserName}},<br/><p>First part of the email body goes here");
Template bodyTemplate = Template.Parse(templateSource); // Parses and compiles the template source

//Create DTO for the renderer
var bodyDto = new {
    Heading = "Heading Here",
    UserName = userName
};
String bodyText = bodyTemplate.Render(Hash.FromAnonymousObject(bodyDto));

It also works with collections, see some online examples.

jacobsgriffith
  • 1,448
  • 13
  • 18
Marcel
  • 15,039
  • 20
  • 92
  • 150
  • can templateSource be a .html file? or better yet a .cshtml razor file? – Andy Oct 23 '15 at 18:45
  • 1
    @Ozzy It can actually be any (text) file. DotLiquid even allows changing the template syntax in case it interferes with your template file. – Marcel Oct 24 '15 at 20:34
5

As an alternative to MailDefinition, have a look at RazorEngine https://github.com/Antaris/RazorEngine.

This looks like a better solution.

Attributted to...

how to send email wth email template c#

E.g

using RazorEngine;
using RazorEngine.Templating;
using System;

namespace RazorEngineTest
{
    class Program
    {
        static void Main(string[] args)
        {
    string template =
    @"<h1>Heading Here</h1>
Dear @Model.UserName,
<br />
<p>First part of the email body goes here</p>";

    const string templateKey = "tpl";

    // Better to compile once
    Engine.Razor.AddTemplate(templateKey, template);
    Engine.Razor.Compile(templateKey);

    // Run is quicker than compile and run
    string output = Engine.Razor.Run(
        templateKey, 
        model: new
        {
            UserName = "Fred"
        });

    Console.WriteLine(output);
        }
    }
}

Which outputs...

<h1>Heading Here</h1>
Dear Fred,
<br />
<p>First part of the email body goes here</p>

Heading Here

Dear Fred,

First part of the email body goes here

Mick
  • 6,527
  • 4
  • 52
  • 67
5

I would recomend using templates of some sort. There are various different ways to approach this but essentially hold a template of the Email some where (on disk, in a database etc) and simply insert the key data (IE: Recipients name etc) into the template.

This is far more flexible because it means you can alter the template as required without having to alter your code. In my experience your likely to get requests for changes to the templates from end users. If you want to go the whole hog you could include a template editor.

Leather
  • 227
  • 2
  • 6
  • Agreed, All the answers pretty much do the same thing using different methods. String.Format is all you need and you can use any of these create your own template. – user1040975 Mar 25 '20 at 19:53
3

Emitting handbuilt html like this is probably the best way so long as the markup isn't too complicated. The stringbuilder only starts to pay you back in terms of efficiency after about three concatenations, so for really simple stuff string + string will do.

Other than that you can start to use the html controls (System.Web.UI.HtmlControls) and render them, that way you can sometimes inherit them and make your own clasess for complex conditional layout.

Mark Dickinson
  • 6,573
  • 4
  • 29
  • 41
2

If you don't want a dependency on the full .NET Framework, there's also a library that makes your code look like:

string userName = "John Doe";

var mailBody = new HTML {
    new H(1) {
        "Heading Here"
    },
    new P {
        string.Format("Dear {0},", userName),
        new Br()
    },
    new P {
        "First part of the email body goes here"
    }
};

string htmlString = mailBody.Render();

It's open source, you can download it from http://sourceforge.net/projects/htmlplusplus/

Disclaimer: I'm the author of this library, it was written to solve the same issue exactly - send an HTML email from an application.

Vladislav Zorov
  • 2,998
  • 19
  • 32
1

You might want to have a look at some of the template frameworks that are available at the moment. Some of them are spin offs as a result of MVC but that isn't required. Spark is a good one.

Simon Farrow
  • 1,901
  • 2
  • 20
  • 26
1

A commercial version which I use in production and allows for easy maintenance is LimiLabs Template Engine, been using it for 3+ years and allows me to make changes to the text template without having to update code (disclaimers, links etc..) - it could be as simple as

Contact templateData = ...; 
string html = Template
     .FromFile("template.txt")
     .DataFrom(templateData )
     .Render();

Worth taking a look at, like I did; after attempting various answers mentioned here.

Geovani Martinez
  • 2,053
  • 2
  • 27
  • 32