Documentation
Docs

Overview

For mails supastarter uses Maizzle 6, a framework for building HTML emails with Vue SFCs and Tailwind CSS 4.

Why Maizzle

Maizzle lets you author email templates as Vue single-file components with Tailwind CSS, then compiles them to clean HTML with inline styles for maximum email-client compatibility. PostHTML .html templates are not supported.

Providers

In the packages/mail/provider folder you can find the available mail providers.

There are multiple providers available:

To select a provider, update the export in packages/mail/provider/index.ts:

packages/mail/provider/index.ts
export * from "./resend";
// or export * from './postmark';
// or export * from './mailgun';
// or export * from './nodemailer';
// or export * from './console'; // for debugging

For debugging purposes you can set the mail provider to console. This will log the mail output only on the console and not send any mails.

Set from mail address

Next up, set the from mail address via the MAIL_FROM environment variable in your .env.local file. This is the mail address that will be used as the sender of all mails. Please make sure that the mail address and domain are verified in your mail provider.

.env.local
MAIL_FROM="example@example.com"

Mail templates

All your mail templates are located in the packages/mail/emails folder as Vue SFCs (.vue). Shared chrome (Layout, Logo, LinkFallback) lives in packages/mail/components. Preview and build config is in packages/mail/maizzle.config.ts.

You can preview your email templates during development using the mail preview app at http://localhost:3005:

pnpm --filter mail-preview dev

Create a mail template

To create a new mail template, create a new .vue file in the packages/mail/emails directory:

packages/mail/emails/NewMail.vue
<script setup lang="ts">
usePreheader("Your preheader text here");
usePlaintext();

const {
  bodyHtml = "Your email content here.",
  confirmAction = "Continue",
  url = "https://example.com",
  openLinkInBrowser = "If you want to open the link in a different browser than your default one, copy and paste this link:",
} = defineProps<{
  bodyHtml?: string;
  confirmAction?: string;
  url?: string;
  openLinkInBrowser?: string;
}>();
</script>

<template>
  <Layout>
    <p class="m-0 mb-6 text-lg/7 text-muted-foreground" v-html="bodyHtml" />

    <Button
      :href="url"
      class="mb-6 rounded-xl bg-primary text-primary-foreground"
    >
      {{ confirmAction }} →
    </Button>

    <LinkFallback :href="url">
      {{ openLinkInBrowser }}
    </LinkFallback>
  </Layout>
</template>

Templates use Maizzle’s Vue composition helpers (usePreheader, usePlaintext) and shared components from packages/mail/components. Brand colors and type live in the Layout theme block. Send-time values are passed as Maizzle props (not PostHTML expressions).

SaaS ships the Vue sources through Nitro serverAssets (baseName: "mail", {emails,components}/**/*.vue) so serverless renders can materialize layouts at runtime.

Register the mail template

Before you can use your new mail template, you have to register it in the packages/mail/util/templates.ts file, where you can also define the subject:

packages/mail/util/templates.ts
export const mailTemplates = {
  //...
  newMail: {
    file: "emails/NewMail.vue",
    subject: "New Mail",
  },
};

Translations

Mail copy lives in packages/i18n/translations/{locale}/mail.json. Add matching keys for every supported locale (en, de, es, fr). buildLocalizedMailContent localizes the subject and template values (including turning body into bodyHtml) before sendEmail merges caller context into Maizzle props.

Use the mail template

Now you can send a mail with your template using the sendEmail() method from the @repo/mail package (server-side only):

import { sendEmail } from "@repo/mail";

await sendEmail({
  to: "tim@apple.com",
  locale: "en",
  templateId: "newMail",
  context: {
    url: "https://example.com",
  },
});