Email

Render markdown email files and send them through Resend, SendGrid, or Postmark.

Use Email to author Markdown emails and send them through Resend, SendGrid, or Postmark. Frontmatter, optional input validation, and optional CSS all live in the same email file.

Use Email frontmatter for message metadata, Input Schemas for validation, and the provider pages for delivery-specific behavior.

When you need localized copy, use Email i18n for locale-specific Markdown files, ICU message formatting, and Nuxt request-locale reuse with @nuxtjs/i18n.

ViteHub discovers emails under server/emails/.

Getting started

Install the package

Install @vitehub/email and the delivery SDK you plan to use. Add tailwindcss only when you want Tailwind utilities in email styles.

Terminal
pnpm add https://pkg.pr.new/vite-hub/vitehub/@vitehub/email@main resend
Terminal
pnpm add tailwindcss

Configure a provider

Set the top-level email key for provider credentials and app-wide defaults. Keep message-specific fields such as subject, preheader, and delivery in the email file itself.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@vitehub/email/nuxt'],
  email: {
    provider: 'resend',
    apiKey: process.env.RESEND_API_KEY,
    defaults: {
      from: 'Acme <hello@example.com>',
    },
  },
})

Create an email

Email files are Markdown only. Frontmatter stores email metadata. Body content uses Comark/MDC syntax plus the built-in primitives.

welcome.md
---
subject: "Welcome {{ name }}"
preheader: Finish setting up your account
layout: transactional
envelope:
  to:
    - max@example.com
---
```ts vitehub-email-schema
import * as v from 'valibot'

const input = v.object({
  actionUrl: v.pipe(v.string(), v.url()),
  name: v.string(),
})

export default input
```

```css vitehub-email-style
@import "tailwindcss";

.cta {
  border-radius: 12px;
}
```

::container{class="mx-auto max-w-[560px] bg-white"}
  ::heading{level=1 class="text-3xl font-bold text-slate-900"}
  Welcome, {{ name }}
  ::

::text{class="mt-4 text-base leading-6 text-slate-600"}
Confirm your email address to finish setup.
::

::button{href="{{ actionUrl }}" class="mt-6 rounded bg-slate-900 px-4 py-3 text-sm font-semibold text-white"}
Verify email
::
::

Styling

Email styling is user-authored. Add one optional css vitehub-email-style block at the top of the Markdown file, after frontmatter and after the optional ts vitehub-email-schema block when present.

Classes and inline style attributes are preserved from your Markdown. ViteHub then compiles Tailwind utilities when available, inlines the resulting CSS into the final HTML, and keeps media queries in a <style> tag in the document head.

welcome.md
---
subject: Welcome
---
```css vitehub-email-style
@import "tailwindcss";

.cta {
border-radius: 12px;
}
```

::button{class="cta text-sm font-semibold"}
Verify email
::

Built-in primitives

These are the supported Markdown email components:

  • container
  • section
  • row
  • column
  • text
  • heading
  • button
  • link
  • img
  • divider
  • spacer
  • code-inline
  • code-block

They accept normal attrs such as class, style, id, href, src, alt, and data attributes. Unknown custom components are rejected.

Runtime API

FunctionUse it for
renderEmail(name, input?, options?)Return the normalized message with subject, html, text, and envelope without sending it. Use options.locale to select a localized variant.
sendEmail(name, input?, options?)Validate, render, and send one discovered email, with optional send-time recipient and delivery overrides.
createEmailClient(provider?)Send already-rendered payloads directly, or drop to provider-level flows such as manual batching.

Use sendEmail(...) as the default runtime path. Keep template data in the second argument and pass recipients or delivery overrides in the third argument.

await sendEmail('welcome', {
name: 'Max',
actionUrl: 'https://example.com/verify',
}, {
locale: 'en',
to: 'max@example.com',
})

Frontmatter and validation

Use frontmatter for message metadata and use the Markdown body for the email structure. The dedicated reference lives on Email frontmatter.

Input validation is optional. When you want it, embed a ts vitehub-email-schema block at the top of the Markdown file. The full guide lives on Input Schemas.

Limits

  • ViteHub renders discovered Markdown emails only.
  • User-defined custom components are not supported.
  • Tailwind utilities that depend on complex selectors may still be limited by email client behavior.
  • Media queries are preserved, but email clients remain the final constraint.

Users who want a React or Vue authoring stack can still render HTML outside ViteHub and send it through createEmailClient().