@lupinum/nuxt-email
Write typed transactional email as Vue components and render it directly in Nuxt.
[!WARNING] The 1.0.0 beta is published on npm's
nexttag. The three-client beta smoke test passed against beta.1; the full eight-client checklist and an external transactional beta still block stable1.0.0. The unscopednuxt-emailpackage on npm is unrelated to this project.
Why use Nuxt Email?
Nuxt Email turns app/emails/ into one typed template registry. You author normal Vue single-file components. Nitro renders deterministic HTML, plain text, and an optional subject line.
The module uses the same rendering core for production, development preview, and tests. Your application keeps control of delivery, recipients, and provider credentials.
When to use it
Use Nuxt Email for account messages, receipts, alerts, and other transactional email that belongs to a Nuxt application.
Do not use it for marketing campaigns or a workflow shared by non-Nuxt applications. Use a dedicated email framework such as Maizzle for those cases. Nuxt Email does not send messages or manage recipients.
Requirements
- Node.js
^22.18.0 || ^24.11.0 || ^26.0.0. - Nuxt
>=4.5.1 <5. - Vue
^3.5.40.
CI tests every supported Node major. Release consumers verify the declared minimum and current Nuxt 4 and Vue 3 versions without a dependency shim.
Installation
Install and register the module:
pnpm add @lupinum/nuxt-email@next
export default defineNuxtConfig({
modules: ['@lupinum/nuxt-email'],
})
Quick start
Create app/emails/welcome.vue:
<script setup lang="ts">
import { defineEmail } from '@lupinum/nuxt-email/define-email'
const props = defineProps<{
firstName: string
activationUrl: string
}>()
defineEmail({
subject: () => `Welcome aboard, ${props.firstName}`,
})
</script>
<template>
<ETailwind>
<EHtml lang="en">
<EHead>
<title>Activate your account</title>
</EHead>
<EBody class="m-0 bg-slate-100 p-6">
<EPreview>Your account is ready.</EPreview>
<EContainer class="rounded-lg bg-white p-6">
<EHeading class="m-0 text-2xl text-slate-900">Welcome, {{ firstName }}</EHeading>
<EText class="text-slate-600">Finish setting up your account.</EText>
<EButton class="rounded-md bg-blue-600 px-5 py-3 text-white" :href="activationUrl">
Activate account
</EButton>
</EContainer>
</EBody>
</EHtml>
</ETailwind>
</template>
Render the template from Nitro:
export default defineEventHandler(async () => {
return await renderEmail('welcome', {
firstName: 'Ada',
activationUrl: 'https://example.com/activate',
})
})
The template name and props are generated from the Vue file. Invalid names and props fail during type checking. Use a sibling fixture to inspect it in /__email, then pass the rendered result to your provider.
Questions? Ask in the Lupinum OSS Discord.
Agent setup
@lupinum/nuxt-email ships its documentation inside the package for coding
agents such as Claude Code, Codex and Cursor. The pages match the installed
version, so an agent does not follow the website for a different version.
To set it up, give your coding agent this task:
Set up @lupinum/nuxt-email for coding agents as described under "Agent setup" in its README.
Steps for the agent
-
Find
node_modules/@lupinum/nuxt-email/dist/agent/AGENTS.md. Look in the directory of the project that depends on@lupinum/nuxt-emailfirst, then in its parent directories up to the repository root (workspaces can hoist packages). Read it; it lists the documentation pages. -
Add the section below to the project's agent instructions:
AGENTS.md, orCLAUDE.mdif the project has only that file. If it has neither, createAGENTS.md. Write the path relative to the repository root, throughnode_modules/@lupinum/nuxt-email(for exampleapps/web/node_modules/@lupinum/nuxt-email/...in a workspace). Never write a resolved path such asnode_modules/.pnpm/...: it contains the version and breaks after an upgrade. If a section for@lupinum/nuxt-emailalready exists, leave it as it is.Use the path you found in place of the sample path:
## @lupinum/nuxt-email Before you change code that uses @lupinum/nuxt-email, read `node_modules/@lupinum/nuxt-email/dist/agent/AGENTS.md` and the pages it lists. They document the installed version. Prefer them over what you remember about this package and over the website. -
Do not copy the documentation into the project and do not install a skill. The section points into the installed package, so it stays correct after every upgrade or downgrade.
If the file does not exist, the installed version has no packaged documentation. Read the package README and its TypeScript types instead.
Email components
Nuxt Email registers 18 server components for document structure, table layout, text, images, links, Outlook-safe buttons, Markdown, inline code, fonts, previews, and Tailwind.
ETailwind converts compatible Tailwind v4 utilities to inline styles. It keeps media-query and pseudo-class rules in the document head. Configure ECodeBlock only when you need syntax highlighting:
export default defineNuxtConfig({
modules: ['@lupinum/nuxt-email'],
nuxtEmail: {
codeBlock: {
languages: ['typescript', 'vue'],
theme: 'github-dark',
},
},
})
Development preview
Run the Nuxt application and open /__email. The development-only page shows the rendered message, HTML, plain text, subject, viewport sizes, and exact byte count.
Add one sibling fixture file for deterministic sample props. Preview routes and fixtures are excluded from production builds.
import type { EmailComponentProps } from '@lupinum/nuxt-email'
import type WelcomeEmail from './welcome.vue'
export default {
firstName: 'Ada',
activationUrl: 'https://example.com/activate',
} satisfies EmailComponentProps<typeof WelcomeEmail>
Testing
Render a Vue email without starting Nuxt:
import { renderEmailComponent } from '@lupinum/nuxt-email/testing'
import Welcome from './app/emails/welcome.vue'
const result = await renderEmailComponent(Welcome, {
firstName: 'Ada',
activationUrl: 'https://example.com/activate',
})
Use #nuxt-email/testing after nuxt prepare when the template uses configured components such as ECodeBlock.
Compatibility evidence
Nuxt Email does not claim universal email-client or React Email compatibility. The generated conformance report records each compared behavior and each intentional difference.
Complete the manual client QA checklist before a rendering release. Unit tests cannot prove how every email client displays a message.
Package exports
The package has a module entry point plus focused build, production rendering, metadata, testing, and error subpaths. Use @lupinum/nuxt-email/build to compile a server registry and @lupinum/nuxt-email/render for compiled components outside Nitro. The standalone guide explains the build and runtime boundaries. The canonical entry-point table lists every runtime and type-only export.
Documentation
Read the Nuxt Email documentation. Start with the installation guide and component reference. Use the guides for preview, testing, and sending.
See the changelog for released changes.
Contributing and development
Read the contribution guide before you open a pull request. Run the normal handoff gate before you submit a change:
corepack enable
pnpm install --frozen-lockfile
pnpm verify
Maintainers use the protected workflow in Lupinum OSS for releases.
Support and security
Open a GitHub issue for bugs and focused proposals. Join the Lupinum OSS Discord for project discussion.
Use the private security process to report a vulnerability. Do not report a vulnerability in a public issue.
License
Nuxt Email is available under the MIT License. Copyright belongs to Lupinum OG.
Related Packages
A Nuxt module for authoring, previewing, and sending transactional email templat...
A minimal portfolio template and layer built with Nuxt 4, Tailwind CSS 4, and GS...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.1.1 | ^3.5.35 | MIT | |
| 0.1.0 | ^3.5.35 | MIT |
Pre-releases (5)
| Version | Released | Vue | License |
|---|---|---|---|
| 1.0.0-beta.5 | ^3.5.40 | MIT | |
| 1.0.0-beta.4 | ^3.5.40 | MIT | |
| 1.0.0-beta.3 | ^3.5.40 | MIT | |
| 1.0.0-beta.2 | ^3.5.35 | MIT | |
| 1.0.0-beta.1 | ^3.5.35 | MIT |