Developer Reference

Email, explained for
web developers.

A practical guide covering HTML email rendering, transactional sending services, SPF/DKIM/DMARC authentication, and testing tools — everything you need without the marketing fluff.

📄

HTML Email Fundamentals

§

Email HTML is not web HTML. Clients strip or ignore most CSS properties, block external stylesheets, and render in rendering engines you haven't thought about since 2003. The key rules:

  • 📐
    Use tables for layout. Flexbox and CSS Grid are unsupported in Outlook (Windows) and older Gmail apps. Nest <table> elements to build multi-column layouts.
  • 🎨
    Inline every style. Gmail strips <style> blocks in forwarded mail and some mobile views. Use inline style="" on every element, or let a framework/build tool do it for you.
  • 📏
    Max width 600–680px. Most preview panes and mobile clients clip wider emails. Set width="600" on the outer table (HTML attribute, not just CSS) for Outlook compatibility.
  • 🖼️
    Host images on a CDN. Attach width and height attributes directly on <img> tags so layout doesn't break when images are blocked by default. Use alt text that still communicates the message.
  • 🌙
    Support dark mode. Apple Mail, Outlook 2021+, and Gmail app (Android) invert colors in dark mode. Use @media (prefers-color-scheme: dark) in a <style> block — most modern clients support it even when they strip other styles.
  • ✉️
    Always send a plain-text part. MIME multipart/alternative with both text/plain and text/html parts improves deliverability and serves users on screen readers or minimal clients.

Minimal table skeleton

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Your Subject Line</title>
  <!--[if mso]>
  <noscript><xml><o:OfficeDocumentSettings>
    <o:PixelsPerInch>96</o:PixelsPerInch>
  </o:OfficeDocumentSettings></xml></noscript>
  <![endif]-->
</head>
<body style="margin:0;padding:0;background:#f4f4f4;">
  <!-- Outer wrapper -->
  <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
    <tr>
      <td align="center" style="padding:40px 0;">
        <!-- Email card -->
        <table role="presentation" width="600" cellpadding="0" cellspacing="0"
               style="background:#ffffff;border-radius:8px;">
          <tr>
            <td style="padding:40px 48px;">
              <h1 style="margin:0 0 16px;font-family:sans-serif;font-size:24px;">
                Hello!
              </h1>
              <p style="margin:0;font-family:sans-serif;font-size:16px;line-height:1.6;">
                Your message here.
              </p>
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
</html>
🧩

Frameworks & Tooling

§

Writing raw email HTML by hand is painful. These tools abstract the table-soup and inline CSS away.

React Email

Write email templates as React components using purpose-built primitives (Button, Section, Text…). Renders to email-safe HTML with inline styles. Ships a preview dev server.

Open SourceReact / JSX

MJML

XML-like markup (<mj-section>, <mj-text>…) that compiles to cross-client HTML. Battle-tested, huge community, VS Code extension available.

Open SourceCompile step

Maizzle

Tailwind CSS utility classes in email — Maizzle inlines all styles at build time. Good choice if you already think in Tailwind and want rapid prototyping.

Open SourceTailwind

Parcel (email mode)

Parcel's built-in email transformer inlines CSS and optimises images. Works with plain HTML/MJML. Good for teams that want a standard build pipeline.

Open SourceBundler

Handlebars / Nunjucks

Server-side templating for dynamic transactional email. Pair with juice for CSS inlining. Simple, no build step beyond your server rendering.

Open SourceServer-side

Foundation for Emails

Zurb's Inky templating language. Similar idea to MJML — custom tags compile to tables. Less actively maintained but stable and widely used in large orgs.

Open SourceLegacy-friendly
🚀

Transactional Email Services

§

Don't run your own SMTP server for transactional mail — IP reputation, bounce handling, and feedback loops are full-time jobs. Pick a provider.

ProviderFree tierDXREST APISMTP relayBest for
Resend3 000 / mo⭐⭐⭐⭐⭐Modern dev teams, React Email
Postmark100 / mo⭐⭐⭐⭐⭐Deliverability-first transactional
AWS SES62 000 / mo*⭐⭐⭐High volume, AWS shops
SendGrid100 / day⭐⭐⭐⭐Marketing + transactional combo
Mailgun100 / day (trial)⭐⭐⭐⭐API-first, good logs
Brevo (ex-Sendinblue)300 / day⭐⭐⭐Budget-friendly, EU data residency

* AWS SES free tier applies to emails sent from EC2 instances or Lambda. Sending from outside AWS costs $0.10/1 000.

🟩

Sending from Node.js

§

Nodemailer (SMTP)

The classic Node.js mailer. Works with any SMTP relay.

import nodemailer from 'nodemailer';

const transport = nodemailer.createTransport({
  host: 'smtp.postmarkapp.com',
  port: 587,
  auth: {
    user: process.env.POSTMARK_API_KEY,
    pass: process.env.POSTMARK_API_KEY,
  },
});

await transport.sendMail({
  from: '"Acme" <hello@acme.com>',
  to: user.email,
  subject: 'Welcome to Acme',
  text: 'Thanks for signing up.',
  html: '<p>Thanks for signing up.</p>',
});

Resend SDK

Minimal REST wrapper — no SMTP config needed.

import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

const { data, error } = await resend.emails.send({
  from: 'Acme <hello@acme.com>',
  to: [user.email],
  subject: 'Welcome to Acme',
  html: '<p>Thanks for signing up.</p>',
});

if (error) throw new Error(error.message);

Development: catch all outgoing mail locally

# Mailpit — modern Mailhog replacement
docker run -d -p 1025:1025 -p 8025:8025 axllent/mailpit

# Then point Nodemailer at localhost:1025 (no auth)
# Open http://localhost:8025 to read caught mail
🔐

Email Authentication: SPF, DKIM & DMARC

§

Since February 2024, Google and Yahoo require bulk senders to have SPF, DKIM, and DMARC configured. Even for low volume you should set these up — they directly affect inbox placement.

TXTyourdomain.com
SPF — authorises which mail servers may send for your domainv=spf1 include:_spf.google.com include:spf.postmarkapp.com ~all

List every service that sends mail for your domain. End with ~all (softfail) while testing, switch to -all (hardfail) once stable.

TXT / CNAMEpm._domainkey.yourdomain.com
DKIM — cryptographic signature that proves the message wasn't tampered withv=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUA...

Your sending provider generates the key pair. You publish the public key as a DNS record. The selector prefix (pm above) is provider-specific.

TXT_dmarc.yourdomain.com
DMARC — policy for what to do with unauthenticated mail + aggregate reportingv=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com

Start with p=none to monitor, then escalate to quarantinereject once you're confident all legitimate senders pass. rua is where ISPs send aggregate XML reports.

📬

Deliverability

§

Good deliverability is the combined result of technical setup, list hygiene, and sending behaviour. Here's what actually moves the needle:

  • 🏠
    Use a subdomain for transactional mail. Send from mail.acme.com or notifications.acme.com rather than your root domain. Reputation problems on one subdomain don't contaminate the other.
  • 🔁
    Handle bounces and complaints immediately. Subscribe to your provider's webhook events for bounce, complaint, and unsubscribe. Suppress those addresses within seconds — ISPs watch your bounce and complaint rates closely.
  • 📈
    Warm up new IPs and domains. Start with a small volume (a few hundred / day) and ramp up over 2–4 weeks. Jumping from 0 to 50 000 emails/day triggers spam filters.
  • 🕵️
    Apple Mail Privacy Protection (MPP) inflates open rates. Since iOS 15, Apple pre-fetches tracking pixels for ~60 % of your list. Open rates are no longer a reliable metric. Use click-through rate and conversion instead.
  • 🔗
    One-click unsubscribe (RFC 8058). Required by Google and Yahoo for bulk senders. Add List-Unsubscribe and List-Unsubscribe-Post headers so mail clients display a native unsubscribe button.
🧪

Testing Tools

§

Test at three levels: rendering across clients, catching mail during development, and validating authentication records.

Mailpit

Local SMTP catcher with a web UI. Docker image is tiny. Replaces the unmaintained Mailhog. Run it in dev; zero production risk.

Open SourceFreeLocal dev

Mailtrap

Hosted fake SMTP inbox — safe to use from CI/staging. Also has a real sending infra product and an email preview UI for client rendering checks.

Free tier

Litmus

Gold standard for cross-client rendering previews. Tests in 100+ clients including Outlook 2013–2021, Gmail, Apple Mail, and mobile apps.

Rendering

Email on Acid

Litmus competitor with similar client coverage. Often slightly cheaper. Good accessibility checker built in.

Rendering

mail-tester.com

Send a test email to a unique address, get a deliverability score. Checks SPF, DKIM, DMARC, spam-filter heuristics, and HTML size.

Free (3/day)Deliverability

MXToolbox

DNS record lookup, blacklist checks, and SMTP diagnostics. Paste your domain and see SPF/DKIM/DMARC parsed and validated instantly.

FreeDNS / Auth