Skip to content

Email Templates

@matthesketh/utopia-email provides template-based email rendering using UtopiaJS components. Render .utopia components to email-safe HTML with automatic CSS inlining and plain text fallback generation. Adapter pattern for sending via SMTP, Resend, or SendGrid.

Quick Start

bash
pnpm add @matthesketh/utopia-email
ts
import { createMailer } from '@matthesketh/utopia-email';
import { smtpAdapter } from '@matthesketh/utopia-email/smtp';
import WelcomeEmail from './emails/Welcome.utopia';

const mailer = createMailer(smtpAdapter({
  host: 'smtp.example.com',
  port: 587,
  auth: { user: 'user', pass: 'pass' },
}));

await mailer.send({
  to: '[email protected]',
  from: '[email protected]',
  subject: 'Welcome!',
  component: WelcomeEmail,
  props: { name: 'Alice' },
});

Rendering Pipeline

renderEmail() orchestrates the full pipeline from component to email-ready HTML + plain text:

  .utopia component + props

     1. renderToString(component, props)

     { html: bodyHtml, css }

     2. inlineCSS(bodyHtml, css)

     bodyHtml with style="" attributes

     3. wrapEmailDocument({ bodyHtml, css, previewText, ... })

     full HTML document (<!DOCTYPE html>...)

     4. htmlToText(fullHtml)

     plain text fallback

     { html, text, subject? }

renderEmail(component, props?, options?)

ts
import { renderEmail } from '@matthesketh/utopia-email';
import WelcomeEmail from './emails/Welcome.utopia';

const { html, text, subject } = renderEmail(WelcomeEmail, { name: 'Alice' }, {
  subject: 'Welcome!',
  previewText: 'Thanks for signing up',
});

RenderEmailOptions:

FieldTypeDefaultDescription
subjectstring--Email subject line
previewTextstring--Hidden preview text shown in email clients
skipInliningbooleanfalseSkip CSS inlining (keep styles in <style> only)
skipStyleBlockbooleanfalseSkip the <style> block (inline styles only)
headContentstring--Extra HTML to inject into <head>

RenderEmailResult:

FieldTypeDescription
htmlstringFull email HTML document
textstringPlain text fallback
subjectstringSubject if provided via options

Adapters

ProviderImport PathConfig TypePeer Dependency
SMTP@matthesketh/utopia-email/smtpSmtpConfignodemailer
Resend@matthesketh/utopia-email/resendResendConfigresend
SendGrid@matthesketh/utopia-email/sendgridSendGridConfig@sendgrid/mail

All provider SDKs are optional peer dependencies. Install only the one you need.

Config Types

ts
interface SmtpConfig {
  host: string;
  port: number;
  secure?: boolean;
  auth?: { user: string; pass: string };
}

interface ResendConfig {
  apiKey: string;
}

interface SendGridConfig {
  apiKey: string;
}

Adapter Example

ts
import { createMailer } from '@matthesketh/utopia-email';
import { resendAdapter } from '@matthesketh/utopia-email/resend';

const mailer = createMailer(resendAdapter({
  apiKey: process.env.RESEND_API_KEY!,
}));

Email Components

Built-in components for common email patterns. These are standard UtopiaJS components that render to table-based, email-client-safe HTML.

ComponentDescription
EmailLayoutTop-level email wrapper (sets width, background, font stack)
EmailButtonCall-to-action button (table-based for Outlook compatibility)
EmailCardBordered content card with padding
EmailDividerHorizontal rule / visual separator
EmailHeadingHeading text (h1-h6)
EmailTextBody text paragraph
EmailImageResponsive image with alt text
EmailColumnsMulti-column layout (table-based)
EmailSpacerVertical spacing element
ts
import {
  EmailLayout,
  EmailButton,
  EmailCard,
  EmailHeading,
  EmailText,
} from '@matthesketh/utopia-email';

Use these inside .utopia email templates:

html
<template>
  <EmailLayout>
    <EmailCard>
      <EmailHeading>Welcome, {{ name() }}!</EmailHeading>
      <EmailText>Thanks for signing up.</EmailText>
      <EmailButton href="https://example.com/dashboard">
        Get Started
      </EmailButton>
    </EmailCard>
  </EmailLayout>
</template>

<script>
import { signal } from '@matthesketh/utopia-core';
const props = defineProps<{ name: string }>();
const name = signal(props.name);
</script>

CSS Inlining

inlineCSS(html, css) converts scoped CSS rules into inline style="" attributes on each matching HTML element. This is essential for email rendering because most email clients strip <style> blocks.

The inliner:

  • Parses CSS rules and calculates selector specificity
  • Matches selectors against elements (supports tag, class, ID, attribute, descendant, and child combinators)
  • Merges declarations respecting specificity and source order
  • Preserves existing inline styles (highest priority)
  • Skips @media and other at-rules
ts
import { inlineCSS } from '@matthesketh/utopia-email';

const html = '<div class="card"><p>Hello</p></div>';
const css = '.card { padding: 20px; } .card p { color: #333; }';

const result = inlineCSS(html, css);
// '<div class="card" style="padding: 20px"><p style="color: #333">Hello</p></div>'

Plain Text

htmlToText(html) converts an HTML email document to a plain text fallback suitable for email clients that do not render HTML.

Conversion rules:

  • Links become text (url) format
  • Headings are uppercased with surrounding blank lines
  • <br> becomes newline, <hr> becomes ---
  • List items get - prefix
  • Block elements get trailing newlines, table cells get tabs
  • HTML entities are decoded
  • Whitespace is collapsed and lines are trimmed
ts
import { htmlToText } from '@matthesketh/utopia-email';

const text = htmlToText(emailHtml);

API Reference

createMailer(adapter)

Create a mailer instance with the given adapter.

Returns: Mailer with a single send(options) method.

mailer.send(options)

Render a component and send the email.

MailerSendOptions:

FieldTypeDescription
tostring | string[]Recipient(s) (required)
fromstringSender address (required)
subjectstringEmail subject
componentanyCompiled .utopia component (required)
propsRecord<string, any>Props to pass to the component
renderOptionsRenderEmailOptionsRendering options (previewText, skipInlining, etc.)
ccstring | string[]CC recipients
bccstring | string[]BCC recipients
replyTostringReply-to address
attachmentsEmailAttachment[]File attachments

Returns: Promise<EmailResult> with { success: boolean, messageId?: string, error?: string }.

renderEmail(component, props?, options?)

Render a component to email HTML + plain text without sending. See Rendering Pipeline above.

inlineCSS(html, css)

Inline CSS declarations into HTML style="" attributes.

Parameters:

  • html -- Well-formed HTML string
  • css -- CSS string (scoped styles)

Returns: HTML string with inline styles applied.

htmlToText(html)

Convert HTML to plain text.

Parameters:

  • html -- HTML string

Returns: Plain text string.

Type Reference

All types are exported from @matthesketh/utopia-email.

TypeDescription
RenderEmailOptionsOptions for renderEmail()
RenderEmailResultResult of renderEmail() (html, text, subject)
EmailAdapterAdapter interface (send(message))
EmailMessageFull email message (to, from, subject, html, text, cc, bcc, ...)
EmailResultSend result (success, messageId, error)
EmailAttachmentAttachment (filename, content, contentType, encoding)
MailerSendOptionsOptions for mailer.send()
SmtpConfigSMTP adapter configuration
ResendConfigResend adapter configuration
SendGridConfigSendGrid adapter configuration

Released under the MIT Licence.