HTML Email Rendering
The Pragmatic.Documents.Email package renders an EmailModel to email-client-safe HTML. The model is defined in Pragmatic.Email.Model and uses an idiomatic section → column → node builder API.
Why a dedicated email model?
Section titled “Why a dedicated email model?”HTML email is not HTML. The big differences:
| HTML | HTML email |
|---|---|
| CSS classes, stylesheets | Inline styles (Outlook ignores <style> half the time) |
| Modern layout (flex, grid) | Nested <table>s — tables are the safe layout primitive |
| No fixed width | Wrapper width (typically 600px) for predictable rendering |
Preheader in <head> | Hidden preheader as the first text element |
| Images rendered freely | Images often blocked — need alt text and fallback |
Pragmatic.Documents.Email abstracts all of this. You describe sections and columns; the renderer produces HTML that works in Gmail, Outlook (including MSO-conditional tables for 2007+), Apple Mail, iOS/Android clients, and webmail.
API surface
Section titled “API surface”public sealed class EmailHtmlRenderer{ public string Render(EmailModel model); public void RenderTo(TextWriter writer, EmailModel model);}
public sealed class EmailBuilder{ public EmailBuilder Subject(string subject); public EmailBuilder Preheader(string preheader); public EmailBuilder Language(string language); public EmailBuilder Width(int width); public EmailBuilder BackgroundColor(string color); public EmailBuilder WrapperBackgroundColor(string color); public EmailBuilder FontFamily(string fontFamily); public EmailBuilder FontSize(int fontSize); public EmailBuilder TextColor(string color);
// Section types public EmailBuilder Section(Action<SectionBuilder> configure); public EmailBuilder FullWidthSection(Action<ColumnBuilder> configure, string? backgroundColor = null); public EmailBuilder Hero(Action<HeroBuilder> configure, string? backgroundColor = null); public EmailBuilder TwoColumns(Action<ColumnBuilder> left, Action<ColumnBuilder> right, ...); public EmailBuilder ThreeColumns(...); public EmailBuilder Article(Action<ArticleBuilder> configure, string? backgroundColor = null); public EmailBuilder Footer(Action<ColumnBuilder> configure, string? backgroundColor = null); public EmailBuilder SocialBar(IEnumerable<SocialLink> links, string? backgroundColor = null);
public EmailModel Build();}Minimal example
Section titled “Minimal example”using Pragmatic.Email.Model;using Pragmatic.Documents.Email;
var email = new EmailBuilder() .Subject("Welcome to Pragmatic") .Preheader("Your account is ready.") .Width(600) .Section(s => s .Column(c => c .Heading("Welcome, Alice!") .Text("Thanks for signing up.") .Button("Get started", "https://app.acme.com/start"))) .Build();
string html = new EmailHtmlRenderer().Render(email);Section types
Section titled “Section types”Generic section
Section titled “Generic section”Sections are the default container — they accept one or more columns.
.Section(s => s .BackgroundColor("#ffffff") .Padding(24) .Column(c => c .Heading("Section heading") .Text("Section body.")))A hero has background colour / image and a prominent heading.
.Hero(h => h .BackgroundImage("https://cdn.acme.com/banner.jpg") .Heading("Spring sale") .Text("Save up to 30%") .Button("Shop now", "https://shop.acme.com"))Two / three columns
Section titled “Two / three columns”Side-by-side columns that collapse to a vertical stack on narrow screens (responsive CSS, no media-query gymnastics in your code).
.TwoColumns( left: l => l.Heading("Feature A").Text("Description of feature A."), right: r => r.Heading("Feature B").Text("Description of feature B."))Article
Section titled “Article”A classic article block with a heading, body text, and optional CTA.
.Article(a => a .Heading("What's new in 0.8") .Paragraph("40 modules, 42 samples, end-to-end Showcase.") .Button("Read the release notes", "https://pragmaticdesign.net/0.8"))Footer + social bar
Section titled “Footer + social bar”.Footer(f => f .Text("You're receiving this email because you signed up at acme.com.", color: "#666", align: EmailTextAlign.Center) .Text("123 Main Street, Lisbon, Portugal", color: "#666", align: EmailTextAlign.Center)).SocialBar(new[]{ new SocialLink { Platform = "twitter", Url = "https://twitter.com/acme" }, new SocialLink { Platform = "github", Url = "https://github.com/acme" },})Column building blocks
Section titled “Column building blocks”Inside a column you assemble EmailNodes via the ColumnBuilder:
| Method | Produces |
|---|---|
.Heading(content, level, align) | <h1..h6> with inline style |
.Text(content, align, color) | Paragraph with inline style |
.Image(src, alt, width, link) | <img> with optional wrapping <a> |
.Button(text, href, bg) | Bulletproof button (MSO-conditional) |
.Spacer(height) | Vertical spacing row |
.Divider(color) | Horizontal rule |
Each helper returns the builder, so you can chain them.
Client compatibility
Section titled “Client compatibility”The renderer emits:
- DOCTYPE — HTML 4.01 Transitional (most compatible)
- Meta tags — charset UTF-8, viewport, colour-scheme
- MSO conditionals —
<!--[if mso]>blocks for Outlook-specific layout - Inline CSS on every element — no reliance on
<style> - Preheader — hidden text as the first body element
- Fallbacks — buttons are bulletproof (anchor + table + MSO VML), images have
alttext, font stacks include email-safe fallbacks
Tested against Gmail (web + iOS + Android), Outlook 2016/2019/365, Apple Mail, iOS Mail, Yahoo Mail, Outlook.com. For older Outlook (2007-2010) MSO-conditional blocks keep the layout intact.
Delivering the email
Section titled “Delivering the email”Pragmatic.Documents.Email produces HTML — it doesn’t send email. Hand the result to:
Pragmatic.Email— transactional email with transport abstraction (SMTP, SendGrid, SES)- Your existing SMTP library
- A provider SDK (Postmark, SendGrid, Mailgun)
var email = BuildVerificationEmail(userEmail, verifyLink);string html = new EmailHtmlRenderer().Render(email);
await smtpClient.SendAsync(new Message{ Subject = email.Subject, To = userEmail, HtmlBody = html, TextBody = StripToPlainText(html), // always include a text alternative});For multipart alternatives (text/plain + text/html), render both: the model carries the subject and preheader; convert the column content to plain text yourself.
Limitations
Section titled “Limitations”- No support for AMP email (
<amp-carousel>, forms) — plain HTML email only. - No built-in i18n — use
Pragmatic.Documents.Templating.I18N+ a template. - No A/B test variants or preview images — those belong to your ESP.
- Dark-mode tuning is opt-in per design — the renderer uses
color-scheme: light darkbut doesn’t auto-adjust colour choices.
Related
Section titled “Related”- markup-parser.md —
.pdxemailmarkup →EmailTemplate - templating.md — bind dynamic data into email templates
Pragmatic.Documents.Email.Samples— hero, two-column, footer, markup-bound