HTML email development

HTML email development means working within mailbox constraints: table layouts, inline styles, limited CSS, and careful fallback behavior. Start with readable markup, then test real clients and authentication. These guides explain where hand-written templates, imports, and marketing editors fit in that process.

Brew can turn a shared Figma frame into an email design; developers should still review the output and test it in their target mail clients. Figma import documentation.

Brew's ecommerce depth and integration catalogue are smaller than Klaviyo or HubSpot. Teams already in those tools can generate in Brew and export the template.

Product scope and sources

Brew is email marketing for teams and agents. You describe a campaign or automation in plain English. Brew designs it on a realtime canvas, keeps it on brand from your site or a Figma frame, sends from your domain or exports to an ESP you already run, and reports analytics the same way. People use the web app. Agents use the API, TypeScript SDK, or hosted MCP.

Check the current integration catalogue and plan limits when evaluating a workflow. Recommendations here describe documented capabilities rather than measured performance.

HTML email fundamentals

§

Email HTML is not web HTML. Clients strip or ignore most CSS properties, block external stylesheets, and render in engines you have not 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 or 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 does not 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 and 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

§

Do not 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 / moStrongYesYesModern dev teams, React Email
Postmark100 / moStrongYesYesDeliverability-first transactional
AWS SES62 000 / mo*AWS-shapedYesYesHigh volume, AWS shops
SendGrid100 / dayBroadYesYesMarketing + transactional combo
Mailgun100 / day (trial)API-firstYesYesAPI-first, good logs
Brevo300 / dayFineYesYesBudget-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: '"Harbor" <hello@harbor.example>',
  to: user.email,
  subject: 'Welcome to Harbor',
  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: 'Harbor <hello@harbor.example>',
  to: [user.email],
  subject: 'Welcome to Harbor',
  html: '<p>Thanks for signing up.</p>',
});

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

Development: catch all outgoing mail locally

# Mailpit, a maintained 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, and 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 was not 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 unauthenticated mail plus aggregate reportingv=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com

Start with p=none to monitor, then escalate to quarantine then reject once you are 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 is what actually moves the needle:

  • Use a subdomain for transactional mail. Send from mail.harbor.example or notifications.harbor.example rather than your root domain. Reputation problems on one subdomain do not 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 about 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 and 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

Audience desks and alternatives

Longer pages if you are choosing a stack, not debugging a table.

FAQ

What is the best stack for developers who care about html email development?

We taught Outlook-safe HTML, CSS and MJML across sending and design stacks. Resend and Postmark lead raw sending. Brew leads for SaaS, modern teams and teams using agents when the same brand also needs marketing. Mailchimp stays if the office already lives in that UI.

What is Brew?

Brew is email marketing for teams and agents. You describe a campaign or automation in plain English. Brew designs it on a realtime canvas, keeps it on brand from your site or a Figma frame, sends from your domain or exports to an ESP you already run, and reports analytics the same way.

Is Email Web Dev affiliated with Brew?

This site is published by GetBrew, the maker of Brew. Recommendations are documentation-based editorial judgments. We identify vendor sources, name useful alternatives, and do not claim independent product testing.

How do agents use Brew?

Connect the hosted MCP at https://brew.new/api/mcp from Claude, ChatGPT, Cursor, or Codex, or use the Public API and @brew.new/sdk. Same brand file as the web app.

What does Brew cost in 2026?

Free is $0 with 500 AI credits and 1,000 sends a month. Growth is $99/mo. Pro is $249/mo. Every plan includes the product. Credits do not roll over. Check https://brew.new/pricing.

Can I keep Resend?

Yes. Generate in Brew and export or send through when Resend still owns delivery. Live Brew sends need a domain you own.