Migrating from React Email
If you're coming from React Email, Maizzle's API and component model will feel familiar.
Component map
Maizzle has almost 100% parity with React Email's core components, making it very easy to migrate: keep using the same component names, just switch to Vue SFCs instead of JSX.
Here's a quick reference for how React Email components map to Maizzle equivalents:
| React Email | Maizzle |
|---|---|
<Html> | Html |
<Head> | Head |
<Body> | Body |
<Container> | Container |
<Section> | Section |
<Row> | Row |
<Column> | Column |
<Heading> | Heading |
<Text> | Text |
<Link> | Link |
<Button> | Button |
<Img> | Img |
<Hr> | Hr |
<Preview> | Preheader |
<CodeBlock> | CodeBlock |
<CodeInline> | CodeInline |
<Markdown> | Markdown |
<Font> | Font |
<Tailwind> | Tailwind |
Most components can do even more than what you're used to in React Email: the <Button> supports VML fallback for Outlook on Windows, <Img> has built-in support for dark mode fallbacks and a11y, and layout components like <Container> or <Column> work everywhere even when sizing them with Tailwind.
Maizzle components
These have no React Email equivalent, here's some stuff you've been missing out on:
| Component | Purpose |
|---|---|
| Layout | Opinionated skeleton with email-safe defaults |
| Outlook | Render content only for Outlook |
| NotOutlook | Render content everywhere except Outlook |
| OutlookBg | Outlook background images with VML |
| Spacer | Vertical and horizontal spacing |
| NoWidows | Prevent orphaned words |
| Raw | Emit content verbatim, bypassing Vue parsing |
| WithUrl | Add base URLs and tracking query params |
| Plaintext | Render content only in the plaintext output |
| NotPlaintext | Render content everywhere except in the plaintext output |
| QrCode | Generate inline QR codes using tables |
Side-by-side
import {
Html,
Head,
Body,
Container,
Heading,
Text,
Button,
Preview,
Tailwind,
pixelBasedPreset,
} from 'react-email'
interface WelcomeEmailProps {
name: string
}
export function WelcomeEmail({ name }: WelcomeEmailProps) {
return (
<Html lang="en">
<Head />
<Tailwind config={{ presets: [pixelBasedPreset] }}>
<Body className="bg-white font-sans">
<Preview>Welcome to Acme — let's get started.</Preview>
<Container className="p-6">
<Heading className="text-2xl">Hi {name}</Heading>
<Text className="text-slate-600">
Thanks for signing up. Click below to verify your email.
</Text>
<Button
href="https://example.com/verify"
className="bg-blue-600 text-white px-5 py-3 box-border"
>
Verify email
</Button>
</Container>
</Body>
</Tailwind>
</Html>
)
}
WelcomeEmail.PreviewProps = {
name: 'Alex',
} satisfies WelcomeEmailProps
Styling
React Email docs suggest using inline style objects. With Maizzle, you typically use Tailwind, but can also use style objects or inline CSS.
We recommend using Tailwind as it's more powerful, easier to maintain, and our CSS pipeline optimizes it for email clients compatibility.
<Button
href="https://example.com"
style={{
backgroundColor: '#2563eb',
color: '#ffffff',
padding: '12px 20px',
borderRadius: '6px',
}}
>
Verify email
</Button>
If you'd rather stick to object syntax you can, but with Vue syntax:
<Button
href="https://example.com"
:style="{
backgroundColor: '#2563eb',
color: '#ffffff',
padding: '12px 20px',
borderRadius: '6px'
}"
>
Verify email
</Button>
Tailwind CSS
Both frameworks expose a <Tailwind> wrapper, but configuration is passed differently.
In React Email, you pass a JS config object via the config prop, which combines their pixelBasedPreset with theme extensions:
import { Body, Tailwind, pixelBasedPreset } from 'react-email'
export function WelcomeEmail() {
return (
<Tailwind
config={{
presets: [pixelBasedPreset],
theme: {
extend: {
colors: { brand: '#6366f1' },
},
},
}}
>
<Body className="bg-brand">...</Body>
</Tailwind>
)
}
In Maizzle, you pass the config to a slot as CSS, just like you'd expect in Tailwind CSS 4:
<template>
<Tailwind>
<template #config>
@import "@maizzle/tailwindcss";
@theme {
--color-brand: #6366f1;
}
</template>
<Body>...</Body>
</Tailwind>
</template>
@maizzle/tailwindcss is Maizzle's official Tailwind CSS 4 config for email and it's included by default, no need to install or import it manually.
It gives you the email-safe defaults like a px scale instead of rem, HEX colors instead of oklch(), prose typography styles for HTML emails, and even email client targeting variants like gmail: and ios:.
You can safely omit the #config slot and Tailwind CSS will work just fine in Maizzle. Use it only if you need to customize the default configuration: to add custom colors, extend the spacing scale, or define new variants.
Rendering
React Email's render() becomes Maizzle's render():
import { render } from 'react-email'
import { WelcomeEmail } from './emails/welcome'
const html = await render(<WelcomeEmail name="Alex" />)
In Maizzle, render accepts a template path or raw SFC string and an optional config object, instead of an imported component like in React Email.
To get per-render data into a template, pass it in the config and read it with useConfig():
const { html } = await render('emails/welcome.vue', {
recipient: { name: 'Alex' },
})
render() runs the full pipeline — SSR, CSS inlining, transformers, doctype — and returns html plus (optionally) plaintext.
See the API Reference for build, dev server, and per-template options.
Sending emails
Sending doesn't change, the only swap is the render() import.
Resend
import { render } from 'react-email'
import { Resend } from 'resend'
import { WelcomeEmail } from './emails/welcome'
const resend = new Resend(process.env.RESEND_API_KEY)
const html = await render(<WelcomeEmail name="Alex" />)
await resend.emails.send({
from: '[email protected]',
to: '[email protected]',
subject: 'Welcome!',
html,
})
Nodemailer (SMTP)
import { render } from 'react-email'
import { createTransport } from 'nodemailer'
import { WelcomeEmail } from './emails/welcome'
const transporter = createTransport({
host: 'smtp.example.com',
port: 587,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
})
const html = await render(<WelcomeEmail name="Alex" />)
await transporter.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: 'Welcome!',
html,
})