@lupinum/nuxt-email 1.0.0-beta.3 → 1.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +49 -17
  3. package/THIRD_PARTY_NOTICES +4 -4
  4. package/dist/agent/AGENTS.md +43 -0
  5. package/dist/agent/pages/docs/components/code.md +68 -0
  6. package/dist/agent/pages/docs/components/content.md +111 -0
  7. package/dist/agent/pages/docs/components/layout.md +89 -0
  8. package/dist/agent/pages/docs/components/markdown.md +49 -0
  9. package/dist/agent/pages/docs/components/structure.md +122 -0
  10. package/dist/agent/pages/docs/components/tailwind.md +41 -0
  11. package/dist/agent/pages/docs/components.md +56 -0
  12. package/dist/agent/pages/docs/getting-started/choose-a-tool.md +38 -0
  13. package/dist/agent/pages/docs/getting-started/first-email.md +99 -0
  14. package/dist/agent/pages/docs/getting-started/installation.md +85 -0
  15. package/dist/agent/pages/docs/getting-started/render-from-nitro.md +59 -0
  16. package/dist/agent/pages/docs/getting-started.md +64 -0
  17. package/dist/agent/pages/docs/guides/client-compatibility.md +65 -0
  18. package/dist/agent/pages/docs/guides/plain-text.md +42 -0
  19. package/dist/agent/pages/docs/guides/preview-workflow.md +83 -0
  20. package/dist/agent/pages/docs/guides/sending-email.md +132 -0
  21. package/dist/agent/pages/docs/guides/standalone-rendering.md +118 -0
  22. package/dist/agent/pages/docs/guides/subject-lines.md +95 -0
  23. package/dist/agent/pages/docs/guides/tailwind.md +128 -0
  24. package/dist/agent/pages/docs/guides/testing-your-emails.md +118 -0
  25. package/dist/agent/pages/docs/guides.md +32 -0
  26. package/dist/agent/pages/docs/reference/conformance.md +55 -0
  27. package/dist/agent/pages/docs/reference/define-email.md +69 -0
  28. package/dist/agent/pages/docs/reference/errors.md +72 -0
  29. package/dist/agent/pages/docs/reference/migration.md +144 -0
  30. package/dist/agent/pages/docs/reference/module.md +90 -0
  31. package/dist/agent/pages/docs/reference/render-email.md +107 -0
  32. package/dist/agent/pages/docs/reference.md +30 -0
  33. package/dist/module.json +1 -1
  34. package/dist/runtime/components/EButton.d.ts +1 -1
  35. package/dist/runtime/tailwind/engine/css/constants.d.ts +1 -0
  36. package/dist/runtime/tailwind/engine/css/constants.js +1 -0
  37. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +2 -1
  38. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +91 -33
  39. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +7 -2
  40. package/dist/runtime/tailwind/engine/stylesheets.d.ts +3 -3
  41. package/dist/runtime/tailwind/engine/stylesheets.js +141 -34
  42. package/package.json +23 -28
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: "Tailwind"
3
+ description: "ETailwind compiles Tailwind v4 utilities into email-safe inline styles and head rules during server rendering."
4
+ url: "https://nuxt-email.lupinum.com/docs/components/tailwind"
5
+ route: "/docs/components/tailwind"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Tailwind
13
+
14
+ > ETailwind compiles Tailwind v4 utilities into email-safe inline styles and head rules during server rendering.
15
+
16
+ ## ETailwind
17
+
18
+ `ETailwind` is the server-only Tailwind v4 boundary. It compiles utilities used by its subtree during rendering. Wrap the whole document so a `<head>` is available inside the boundary.
19
+
20
+ | Prop | Type | Default | Required |
21
+ | --- | --- | --- | --- |
22
+ | `config` | Tailwind config except `content` | — | No |
23
+ | `theme` | `string` | — | No |
24
+ | `utility` | `string` | — | No |
25
+
26
+ Utility classes on descendants become inline styles when possible. Rules that cannot be inlined move to a `<style>` in `<head>`. Rendering fails if such a rule has no head inside the boundary. Nested `ETailwind` boundaries also fail.
27
+
28
+ The [Tailwind guide](/raw/docs/guides/tailwind.md) owns precedence, tokens, nesting, nested components, dark and responsive behavior, animation, limits, and compatibility evidence.
29
+
30
+ ```vue
31
+ <ETailwind>
32
+ <EHtml lang="en">
33
+ <EHead />
34
+ <EBody class="bg-gray-100">
35
+ <EContainer class="p-4">
36
+ <EText class="text-red-500">Styled with Tailwind</EText>
37
+ </EContainer>
38
+ </EBody>
39
+ </EHtml>
40
+ </ETailwind>
41
+ ```
@@ -0,0 +1,56 @@
1
+ ---
2
+ title: "Overview"
3
+ description: "The built-in E-prefixed components, optional ECodeBlock, and their shared safety rules."
4
+ url: "https://nuxt-email.lupinum.com/docs/components"
5
+ route: "/docs/components"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Overview
13
+
14
+ > The built-in E-prefixed components, optional ECodeBlock, and their shared safety rules.
15
+
16
+ Nuxt Email provides **eighteen built-in** E-prefixed components. They are auto-imported into email templates — you never import them. Configuring `codeBlock` adds `ECodeBlock`. All composition uses ordinary Vue slots, and all attributes use Vue and HTML names rather than React prop aliases.
17
+
18
+ ## The components
19
+
20
+ <cards cols="2">
21
+ <card title="Structure" description="EHtml, EHead, EBody, EPreview, EFont" icon="lucide:layout-template" to="/docs/components/structure" />
22
+
23
+ <card title="Layout" description="EContainer, ESection, ERow, EColumn" icon="lucide:columns-3" to="/docs/components/layout" />
24
+
25
+ <card title="Content" description="EText, EHeading, ELink, EImg, EHr, EButton" icon="lucide:type" to="/docs/components/content" />
26
+
27
+ <card title="Code" description="ECodeInline and optional ECodeBlock" icon="lucide:code" to="/docs/components/code" />
28
+
29
+ <card title="Markdown" description="EMarkdown" icon="lucide:file-text" to="/docs/components/markdown" />
30
+
31
+ <card title="Tailwind" description="ETailwind" icon="lucide:paintbrush" to="/docs/components/tailwind" />
32
+ </cards>
33
+
34
+ | Component | Reference |
35
+ | --- | --- |
36
+ | `EHtml`, `EHead`, `EBody`, `EPreview`, `EFont` | [Structure](/raw/docs/components/structure.md#ehtml), [EHead](/raw/docs/components/structure.md#ehead), [EBody](/raw/docs/components/structure.md#ebody), [EPreview](/raw/docs/components/structure.md#epreview), [EFont](/raw/docs/components/structure.md#efont) |
37
+ | `EContainer`, `ESection`, `ERow`, `EColumn` | [EContainer](/raw/docs/components/layout.md#econtainer), [ESection](/raw/docs/components/layout.md#esection), [ERow](/raw/docs/components/layout.md#erow), [EColumn](/raw/docs/components/layout.md#ecolumn) |
38
+ | `EText`, `EHeading`, `ELink`, `EImg`, `EHr`, `EButton` | [EText](/raw/docs/components/content.md#etext), [EHeading](/raw/docs/components/content.md#eheading), [ELink](/raw/docs/components/content.md#elink), [EImg](/raw/docs/components/content.md#eimg), [EHr](/raw/docs/components/content.md#ehr), [EButton](/raw/docs/components/content.md#ebutton) |
39
+ | `ECodeInline`, `ECodeBlock` | [ECodeInline](/raw/docs/components/code.md#ecodeinline), [ECodeBlock](/raw/docs/components/code.md#ecodeblock) |
40
+ | `EMarkdown` | [EMarkdown](/raw/docs/components/markdown.md#emarkdown) |
41
+ | `ETailwind` | [ETailwind](/raw/docs/components/tailwind.md#etailwind) |
42
+
43
+ ## Rules every component shares
44
+
45
+ - **Native attributes and `class` are forwarded** unless a component fixes an email-safety invariant (documented per component).
46
+ - **`style` accepts the normal Vue object, string, or array forms.** Object keys may be camelCase or kebab-case.
47
+ - **Vue interpolation and attribute serialization escape text by default.**
48
+ - **No raw HTML, no scripts.** Every component rejects `innerHTML`, `textContent`, and any attribute name beginning with `on` (case-insensitively). There is no raw-HTML primitive, and event handlers have no place in rendered email.
49
+ - **Required strings must be non-empty at runtime** — for example `href` and `src`.
50
+ - **No React shorthand props.** React Email conveniences such as `mx` are not supported; use ordinary CSS in `style`.
51
+
52
+ ## A note on security
53
+
54
+ Nuxt Email renders trusted application templates. Supply untrusted values through normal escaped Vue bindings. It is **not** an HTML sanitizer.
55
+
56
+ General `href` and `src` values are escaped, but their URL schemes are not validated. Validate application-controlled URLs before rendering. `EMarkdown` is stricter: it rejects raw HTML and unsafe URL schemes. See the renderer's [security boundary](/raw/docs/reference/render-email.md#security-boundary).
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: "Choose the right tool"
3
+ description: "Compare Nuxt Email with the closest Vue, Nuxt, standalone, and React email workflows."
4
+ url: "https://nuxt-email.lupinum.com/docs/getting-started/choose-a-tool"
5
+ route: "/docs/getting-started/choose-a-tool"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Choose the right tool
13
+
14
+ > Compare Nuxt Email with the closest Vue, Nuxt, standalone, and React email workflows.
15
+
16
+ Email tools often share components, Tailwind support, and a preview. The important difference is where templates run and who owns the surrounding workflow.
17
+
18
+ ## Product boundaries
19
+
20
+ | Tool | Use it when |
21
+ | --- | --- |
22
+ | **Nuxt Email** | One Nuxt application owns request-time transactional rendering. Template names and Vue props become one typed Nitro API. Preview, tests, and production use the same renderer; delivery stays in the application. |
23
+ | **[Nuxt Email Renderer](https://nuxt.com/modules/nuxt-email-renderer) / [Vue Email](https://vuemail.net/getting-started)** | You want a broader Vue component and utility model. Nuxt Email Renderer adds a Nuxt-focused live preview and documented internationalization support. Vue Email also documents provider integrations and Nuxt support. Compare their current configuration and delivery model with your application needs. |
24
+ | **[Maizzle](https://maizzle.com/docs/introduction)** | You need a standalone or Vite-based email project, static builds, marketing workflows, or a configurable transformation pipeline. Maizzle 6 uses Vue single-file components and Tailwind CSS 4, and it also has an official [Nuxt integration](https://maizzle.com/docs/installation/frameworks/nuxt). |
25
+ | **[React Email](https://react.email/docs/introduction)** | Your application uses React, or your team wants React Email's component ecosystem and direct [provider integration guides](https://react.email/docs/integrations/overview). Its render utility produces HTML and plain text at send time. |
26
+
27
+ Claims on this page were checked against the linked primary documentation on 2026-08-22. Product boundaries can change, so check those sources before a migration.
28
+
29
+ ## Choose Nuxt Email when
30
+
31
+ - Email templates belong to one Nuxt application.
32
+ - You want template names and props checked at the Nitro call site.
33
+ - You want email-safe Tailwind v4 without loading the application's browser CSS.
34
+ - You want rendering only. Your application will keep recipients, credentials, attachments, and delivery policy.
35
+
36
+ Choose another tool when its boundary matches the job more closely. Nuxt Email does not provide a campaign system, a provider abstraction, a general registry protocol, or a client-side renderer.
37
+
38
+ Next, [install the published beta](/raw/docs/getting-started/installation.md).
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: "Your first email"
3
+ description: "Write a Vue email template under app/emails/, add a typed preview fixture, and understand template naming."
4
+ url: "https://nuxt-email.lupinum.com/docs/getting-started/first-email"
5
+ route: "/docs/getting-started/first-email"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Your first email
13
+
14
+ > Write a Vue email template under app/emails/, add a typed preview fixture, and understand template naming.
15
+
16
+ Every `.vue` file under `app/emails/` is an email template. Nested paths become slash-separated names; `app/emails/account/reset-password.vue` is named `account/reset-password`. The `app/emails/components/` folder is reserved for your own supporting components and is not discovered as a template.
17
+
18
+ ## Write the template
19
+
20
+ <card title="Complete-document requirement" icon="lucide:alert-triangle">
21
+ A template must render exactly one `<html>` root containing exactly one `<body>`. `EHtml` and `EBody` are the supported wrappers. Fragments, text-only roots, and multiple roots fail with `EmailRenderError`. The renderer does not repair or wrap an incomplete document.
22
+ </card>
23
+
24
+ ```vue [app/emails/welcome.vue]
25
+ <script setup lang="ts">
26
+ import { defineEmail } from '@lupinum/nuxt-email/define-email'
27
+
28
+ const props = defineProps<{
29
+ firstName: string
30
+ activationUrl: string
31
+ }>()
32
+
33
+ defineEmail({
34
+ subject: () => `Welcome aboard, ${props.firstName}`,
35
+ })
36
+ </script>
37
+
38
+ <template>
39
+ <ETailwind>
40
+ <EHtml lang="en">
41
+ <EHead>
42
+ <title>Activate your account</title>
43
+ </EHead>
44
+ <EBody class="m-0 bg-slate-100 p-6">
45
+ <EPreview>Your account is ready.</EPreview>
46
+ <EContainer class="rounded-lg bg-white p-6">
47
+ <EHeading class="m-0 text-2xl text-slate-900">Welcome, {{ firstName }}</EHeading>
48
+ <EText class="text-slate-600">Finish setting up your account.</EText>
49
+ <EButton class="rounded-md bg-blue-600 px-5 py-3 text-white" :href="activationUrl">
50
+ Activate account
51
+ </EButton>
52
+ </EContainer>
53
+ </EBody>
54
+ </EHtml>
55
+ </ETailwind>
56
+ </template>
57
+ ```
58
+
59
+ You do not import the `E*` components — the module auto-imports eighteen built-ins and adds `ECodeBlock` when configured. Use normal `defineProps()`, slots, `v-if`, `v-for`, Tailwind v4 classes, HTML attributes, and Vue style bindings. `defineEmail` is an explicit server-only import; its closure captures the template's actual props.
60
+
61
+ ## Standalone renderer boundary
62
+
63
+ Email templates are `.vue` files compiled in a dedicated Nitro Rollup graph. Each render uses a fresh Vue server-side rendering (SSR) application. Vue compiler macros such as `defineProps`, `defineOptions`, and `defineSlots` remain available without imports. Import other Vue APIs explicitly:
64
+
65
+ ```vue
66
+ <script setup lang="ts">
67
+ import { computed } from 'vue'
68
+
69
+ const props = defineProps<{ firstName: string }>()
70
+ const greeting = computed(() => `Welcome, ${props.firstName}`)
71
+ </script>
72
+ ```
73
+
74
+ Nuxt app auto-imports and host application context are not part of the email-rendering contract. Do not rely on these features inside a template:
75
+
76
+ - bare APIs such as `ref`;
77
+ - `useNuxtApp` or context-dependent composables;
78
+ - application plugins; or
79
+ - app-level provides.
80
+
81
+ Pass application data through typed props. Import standalone utilities explicitly. This boundary keeps output deterministic and lets the package testing helper use the same component.
82
+
83
+ ## Add a preview fixture
84
+
85
+ To see the template in the [development preview](/raw/docs/guides/preview-workflow.md), add one exact sibling `.fixtures.ts` file with deterministic sample props:
86
+
87
+ ```ts [app/emails/welcome.fixtures.ts]
88
+ import type { EmailComponentProps } from '@lupinum/nuxt-email'
89
+ import type WelcomeEmail from './welcome.vue'
90
+
91
+ export default {
92
+ firstName: 'Ada',
93
+ activationUrl: 'https://example.com/activate',
94
+ } satisfies EmailComponentProps<typeof WelcomeEmail>
95
+ ```
96
+
97
+ The file must default-export one object, and only the exact `.fixtures.ts` suffix is recognized. The `satisfies` expression makes fixture values obey the same prop type used by the generated render API. Fixtures are excluded from production builds — never import them into production code.
98
+
99
+ Next, [open the development preview](/raw/docs/guides/preview-workflow.md) and inspect the fixture.
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: "Installation"
3
+ description: "Add @lupinum/nuxt-email to a supported Nuxt application and optionally configure syntax highlighting."
4
+ url: "https://nuxt-email.lupinum.com/docs/getting-started/installation"
5
+ route: "/docs/getting-started/installation"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Installation
13
+
14
+ > Add @lupinum/nuxt-email to a supported Nuxt application and optionally configure syntax highlighting.
15
+
16
+ Nuxt Email installs into an existing, supported Nuxt 4 application. Its email discovery and rendering conventions require no configuration.
17
+
18
+ ## 1. Add the Nuxt module
19
+
20
+ ```bash
21
+ pnpm add @lupinum/nuxt-email@next
22
+ ```
23
+
24
+ <card title="Use the scoped name" icon="lucide:info">
25
+ The unscoped `nuxt-email` package on npm is unrelated to this project. Always install and import `@lupinum/nuxt-email`.
26
+ </card>
27
+
28
+ Register the unversioned package name in Nuxt configuration:
29
+
30
+ ```ts [nuxt.config.ts]
31
+ export default defineNuxtConfig({
32
+ modules: ['@lupinum/nuxt-email'],
33
+ })
34
+ ```
35
+
36
+ ### Set up a coding agent
37
+
38
+ The package ships its documentation for coding agents such as Claude Code,
39
+ Codex and Cursor, so they read pages that match the installed version. Give
40
+ your coding agent this task:
41
+
42
+ ```text
43
+ Set up @lupinum/nuxt-email for coding agents as described under "Agent setup" in its README.
44
+ ```
45
+
46
+ The steps are in the [README's Agent setup section](https://github.com/lupinum-dev/nuxt-email#agent-setup).
47
+
48
+ ## 2. Confirm the TypeScript entry point
49
+
50
+ If your app does not already have one, add the ordinary Nuxt `tsconfig.json`:
51
+
52
+ ```json [tsconfig.json]
53
+ {
54
+ "extends": "./.nuxt/tsconfig.json"
55
+ }
56
+ ```
57
+
58
+ ## Optional code blocks
59
+
60
+ Syntax highlighting is opt-in. Nuxt Email uses the Shiki syntax highlighter. Configure exactly the languages your templates use and one theme:
61
+
62
+ ```ts
63
+ export default defineNuxtConfig({
64
+ modules: ['@lupinum/nuxt-email'],
65
+ nuxtEmail: {
66
+ codeBlock: {
67
+ languages: ['typescript'],
68
+ theme: 'github-dark',
69
+ },
70
+ },
71
+ })
72
+ ```
73
+
74
+ Without `codeBlock`, `ECodeBlock` is not registered. Shiki is not loaded during module setup or included in the production server bundle.
75
+
76
+ Shiki remains an installation-time dependency of `@lupinum/nuxt-email`. The option controls build and runtime cost, not package download cost. Discovery directories, the preview route, and the render API remain fixed. See the [module behavior reference](/raw/docs/reference/module.md) for the full contract.
77
+
78
+ ## What the module registers
79
+
80
+ - Server-only auto-imports for eighteen built-in `E*` components, plus `ECodeBlock` when configured.
81
+ - A Nitro-only `renderEmail(name, props)` auto-import, typed from `app/emails/`.
82
+ - The explicit `@lupinum/nuxt-email/define-email` entry point for declaring a subject inside a template.
83
+ - A development-only preview application at `/__email` (never present in production output).
84
+
85
+ Next, [author your first email](/raw/docs/getting-started/first-email.md).
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: "Render from Nitro"
3
+ description: "Call the typed, server-only renderEmail auto-import and pass its html and text to your provider SDK."
4
+ url: "https://nuxt-email.lupinum.com/docs/getting-started/render-from-nitro"
5
+ route: "/docs/getting-started/render-from-nitro"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Render from Nitro
13
+
14
+ > Call the typed, server-only renderEmail auto-import and pass its html and text to your provider SDK.
15
+
16
+ `renderEmail(name, props)` is the one public application rendering path. It is a **Nitro-only auto-import** generated from `app/emails/` — available in server handlers and other Nitro server code, never in Vue components, client plugins, or client bundles.
17
+
18
+ ## Call it from a handler
19
+
20
+ ```ts [server/api/welcome.get.ts]
21
+ export default defineEventHandler(async () => {
22
+ const { html, text, subject } = await renderEmail('welcome', {
23
+ firstName: 'Ada',
24
+ activationUrl: 'https://example.com/activate',
25
+ })
26
+
27
+ // Hand these values to your provider SDK. Nuxt Email does not send mail.
28
+ return { html, text, subject }
29
+ })
30
+ ```
31
+
32
+ Do not import `renderEmail` from `@lupinum/nuxt-email` or `#imports`; it is injected as a server auto-import. TypeScript checks the template name `'welcome'` and its exact props.
33
+
34
+ ## The result shape
35
+
36
+ The promise resolves to `RenderedEmail`, which is available as a type-only root import when you need to annotate application code:
37
+
38
+ ```ts
39
+ import type { RenderedEmail } from '@lupinum/nuxt-email'
40
+ ```
41
+
42
+ - `html` begins with the XHTML 1.0 Transitional doctype and is deterministic.
43
+ - `text` is authored metadata when `defineEmail` declares `text`. Otherwise, it is derived from the final HTML.
44
+ - `subject` is present only when the template declares one via [`defineEmail`](/raw/docs/guides/subject-lines.md).
45
+
46
+ Nuxt Email owns the body content. Recipients, sender identity, credentials, attachments, and delivery stay in your provider SDK. See the [renderEmail reference](/raw/docs/reference/render-email.md) for the full contract.
47
+
48
+ ## Generate and verify types
49
+
50
+ The generated server declarations change when templates are added, renamed, or deleted:
51
+
52
+ ```bash
53
+ pnpm exec nuxt prepare
54
+ pnpm exec vue-tsc --noEmit -p .nuxt/tsconfig.server.json
55
+ ```
56
+
57
+ If an editor was open before `nuxt prepare`, restart its TypeScript service once so it reloads the generated server config.
58
+
59
+ Next, [hand the rendered result to your provider](/raw/docs/guides/sending-email.md).
@@ -0,0 +1,64 @@
1
+ ---
2
+ title: "Introduction"
3
+ description: "What Nuxt Email is, why its output is trustworthy, and how it is compared with a fixed React Email version."
4
+ url: "https://nuxt-email.lupinum.com/docs/getting-started"
5
+ route: "/docs/getting-started"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Introduction
13
+
14
+ > What Nuxt Email is, why its output is trustworthy, and how it is compared with a fixed React Email version.
15
+
16
+ Nuxt Email is typed transactional email for Nuxt, with email-safe Tailwind v4. You write Vue single-file components (`.vue` files) in `app/emails/`. The module discovers them, generates typed Nitro render calls, and compiles Tailwind utilities into email output.
17
+
18
+ It does one job and stops there. Nuxt Email does not send mail, choose providers, or manage recipients. You hand the rendered `html`, `text`, and optional computed `subject` to your provider SDK.
19
+
20
+ ## Where it fits
21
+
22
+ Choose Nuxt Email when transactional messages belong to a Nuxt application and should share its Vue authoring model, TypeScript contracts, and Nitro deployment. The generated `renderEmail('welcome', props)` call is the product: no sidecar renderer, separate registry, or module configuration.
23
+
24
+ Nuxt Email is one focused option among Vue, Nuxt, standalone, and React email tools. [Compare the product boundaries](/raw/docs/getting-started/choose-a-tool.md) before installing it.
25
+
26
+ ## Why the output is trustworthy
27
+
28
+ Email HTML is unforgiving: clients strip `<style>`, break flexbox, and mangle padding. Nuxt Email combines conservative primitives with reproducible checks while keeping live-client claims separate.
29
+
30
+ - **Checked against React Email.** Covered behaviors are compared, case by case, with fixed versions of [React Email](https://react.email) (`6.11.0`) and `@react-email/render` (`2.1.0`). The [conformance report](/raw/docs/reference/conformance.md) records the tested scope and intentional differences. It is regression evidence, not a universal inbox-rendering guarantee.
31
+ - **Tailwind v4 for email.** `ETailwind` inlines compatible utilities, preserves author-style precedence, and moves media-query and pseudo-class rules into the head.
32
+ - **Deterministic.** Two renders of the same template and props return byte-identical HTML and a matching plain-text fallback. No IDs, timestamps, hydration payload, or mutable renderer state.
33
+ - **Server-only and typed.** `renderEmail('welcome', props)` is generated from your `app/emails/` folder. Unknown template names, missing props, and wrong types fail at compile time.
34
+ - **Limits are documented.** No global "React Email compatible" claim exists. Differences are recorded per behavior in the [conformance report](/raw/docs/reference/conformance.md) and [migration guide](/raw/docs/reference/migration.md).
35
+
36
+ <cards cols="2">
37
+ <card title="Choose the right tool" description="Compare the closest workflows by where templates run and who owns delivery." icon="lucide:signpost" to="/docs/getting-started/choose-a-tool" />
38
+
39
+ <card title="Install the module" description="Add the scoped package to a supported Nuxt app and register it." icon="lucide:download" to="/docs/getting-started/installation" />
40
+
41
+ <card title="Author your first email" description="Write a Vue .vue file and add a preview fixture." icon="lucide:file-code" to="/docs/getting-started/first-email" />
42
+
43
+ <card title="Render from Nitro" description="Call renderEmail from a server handler and read the result." icon="lucide:server" to="/docs/getting-started/render-from-nitro" />
44
+
45
+ <card title="Component reference" description="The built-in E* primitives and optional syntax-highlighted code blocks." icon="lucide:blocks" to="/docs/components" />
46
+ </cards>
47
+
48
+ ## Release status
49
+
50
+ The 1.0.0 beta is published on npm's [`next` tag](https://www.npmjs.com/package/@lupinum/nuxt-email?activeTab=versions). The [three-client beta smoke test](https://github.com/lupinum-dev/nuxt-email/blob/main/docs/release/client-qa-checklist.md#100-beta1-smoke-test) passed against beta.1. The full eight-client checklist and an external transactional beta still block stable `1.0.0`.
51
+
52
+ The package identity is `@lupinum/nuxt-email`; the unscoped npm package is unrelated.
53
+
54
+ ## Supported environment
55
+
56
+ - **Node.js** `^22.18.0 || ^24.11.0 || ^26.0.0` — the supported even-numbered Node 22, 24, and 26 lines.
57
+ - **Nuxt** `>=4.5.1 <5`.
58
+ - **Vue** `^3.5.40`.
59
+
60
+ Nuxt `4.5.1` and Vue `3.5.40` are the compatibility baseline. Release consumers verify that minimum and the current compatible Nuxt 4 and Vue 3 versions without a dependency shim. CI covers Node 22, 24, and 26. Other Node majors, Nuxt 3, Nuxt 5, edge runtimes, and client-side email rendering are outside the support contract.
61
+
62
+ The Node range follows the supported even-numbered Node release lines that can run the Nuxt baseline. Keeping that range narrow lets the renderer use the platform Nuxt already requires and avoids compatibility code for old or short-lived Node releases.
63
+
64
+ Questions? Ask in the [Lupinum OSS Discord](https://discord.lupinum.com).
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: "Client compatibility and proofs"
3
+ description: "What is automatically proven, and the manual email-client QA checklist that closes the remaining gap."
4
+ url: "https://nuxt-email.lupinum.com/docs/guides/client-compatibility"
5
+ route: "/docs/guides/client-compatibility"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Client compatibility and proofs
13
+
14
+ > What is automatically proven, and the manual email-client QA checklist that closes the remaining gap.
15
+
16
+ Nuxt Email separates automated compatibility evidence from manual client results.
17
+
18
+ ## What is automatically proven
19
+
20
+ The [conformance suite](/raw/docs/reference/conformance.md) compares covered behavior with fixed React Email versions. It checks exact bytes, normalized output, or specific semantics. Presentation tables, Outlook-specific button spacers, preview hiding, font declarations, and Tailwind inlining have regression cases.
21
+
22
+ This is useful implementation evidence, but it is a comparison to a reference renderer, not proof that every email client will display a message identically.
23
+
24
+ ## Completed beta smoke test
25
+
26
+ Matthias completed the owner-run `1.0.0-beta.1` smoke test on 2026-08-22. The same `proof-kit` message passed in Gmail web, Apple Mail on macOS, and Outlook classic on Windows in light and dark modes. The [signed QA record](https://github.com/lupinum-dev/nuxt-email/blob/main/docs/release/client-qa-checklist.md#100-beta1-smoke-test) records the tested commit and artifact hashes.
27
+
28
+ This smoke test is enough for the published beta. It does not complete the full eight-client checklist required for stable `1.0.0`. An external transactional beta also remains pending.
29
+
30
+ ## Run the stable checklist
31
+
32
+ Automated tests do not open real email clients. Run the stable checklist against the fixed `proof-kit` fixture.
33
+
34
+ <steps>
35
+ ### Render the fixture
36
+
37
+ Run `pnpm proofs:generate`, then use the committed `proof-kit` template and props. Optionally replace only the logo URL with an HTTPS image you control.
38
+
39
+ ### Send with your own account
40
+
41
+ Send the exact rendered `html` and `text` to test mailboxes through a provider SDK you choose. Nuxt Email does not send mail.
42
+
43
+ ### Inspect in three clients
44
+
45
+ Open the same message in **Gmail web**, **Apple Mail** (macOS or iOS), and **Outlook for Windows**.
46
+
47
+ ### Record the result
48
+
49
+ Note client versions, screenshots, defects, and a final accept/reject decision.
50
+ </steps>
51
+
52
+ ## Required checks
53
+
54
+ - Inbox preview shows only the intended preview sentence and does not expose filler characters.
55
+ - The remote logo has useful alternative text when images are blocked.
56
+ - Heading, paragraphs, divider, and footer links retain readable spacing.
57
+ - The activation button is clickable, visually centered, and has comparable horizontal and vertical padding **in Outlook**.
58
+ - Two columns stay in one row at desktop email widths and remain readable when the client narrows the message.
59
+ - Link destinations and the `mailto:` link are correct.
60
+ - Plain-text fallback contains the meaningful content and destinations but excludes preview filler and images.
61
+ - No script, hydration payload, framework state, or event-handler attribute is present.
62
+
63
+ <card title="Beta QA is not stable approval" icon="lucide:shield-alert">
64
+ The three-client smoke test is complete. Stable `1.0.0` still waits for the full eight-client record and external beta evidence.
65
+ </card>
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: "Plain text"
3
+ description: "How the plain-text fallback is produced from the rendered HTML, what it excludes, and how to control table output."
4
+ url: "https://nuxt-email.lupinum.com/docs/guides/plain-text"
5
+ route: "/docs/guides/plain-text"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Plain text
13
+
14
+ > How the plain-text fallback is produced from the rendered HTML, what it excludes, and how to control table output.
15
+
16
+ Every render returns a `text` string alongside `html`. By default it is a deterministic fallback produced from the **final rendered HTML**. Nuxt Email uses a fixed `html-to-text` version with no line wrapping.
17
+
18
+ When automatic conversion is not good enough, the template can declare authored text with `defineEmail({ text: '…' })` or a synchronous `text` function. Authored text replaces derivation for that render; it does not create a second template or rendering path.
19
+
20
+ ## What plain text includes and excludes
21
+
22
+ - **Excluded:** head content, images, `<script>`/`<style>` content, and any element marked `data-skip-in-text="true"`. `EPreview` is therefore absent from the fallback.
23
+ - **Links:** destinations are retained when the visible text differs from the URL. Fragment links and `mailto:` links follow the recorded React Email comparison behavior.
24
+ - **Structure:** nested lists, ordered-list starts, blockquotes, hard breaks, preformatted content, Unicode, and ordinary tables all have exact conformance cases.
25
+
26
+ ## Controlling table output
27
+
28
+ By default tables render as plain rows. When you specifically need aligned, column-formatted output for a table, add `data-text-format="dataTable"` to that table:
29
+
30
+ ```vue
31
+ <table data-text-format="dataTable">
32
+ <!-- rows -->
33
+ </table>
34
+ ```
35
+
36
+ ## Inspect it every time
37
+
38
+ Plain text is a real deliverable that some recipients and spam filters read. Inspect it in the [development preview](/raw/docs/guides/preview-workflow.md)'s **Plain text** tab for every transactional template, exactly as you would review the HTML.
39
+
40
+ <card title="ECodeInline appears once" icon="lucide:info">
41
+ `ECodeInline` keeps two copies in HTML for `Orange.fr` compatibility, but marks the hidden copy to be skipped by plain-text generation. Recipients therefore receive the code once in the text alternative. This is an intentional divergence from React Email.
42
+ </card>
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: "Preview workflow"
3
+ description: "Use the development-only /__email preview, add fixtures, and monitor exact rendered size without treating it as a client guarantee."
4
+ url: "https://nuxt-email.lupinum.com/docs/guides/preview-workflow"
5
+ route: "/docs/guides/preview-workflow"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Preview workflow
13
+
14
+ > Use the development-only /__email preview, add fixtures, and monitor exact rendered size without treating it as a client guarantee.
15
+
16
+ Nuxt Email exposes `/__email` while the development server is running. The path follows the configured Nuxt base URL. Open `/__email` with the default base URL, or `/sub/__email` with `app.baseURL: '/sub/'`.
17
+
18
+ The preview lists every template under `app/emails/` and marks templates without fixture data. It shows rendered HTML, exact HTML source, or plain text. The visible page polls once per second, so saved template and fixture changes appear without a restart. Polling pauses in a hidden tab.
19
+
20
+ When Nuxt DevTools is available, its **Nuxt Email** iframe shortcut opens the same baseURL-aware preview. The shortcut adds no second preview application or route.
21
+
22
+ The preview calls the same server-only registry and `renderEmail()` implementation your handlers use. It is a rendering and debugging tool — not a second rendering path and not an email-client compatibility guarantee.
23
+
24
+ ## Add a fixture
25
+
26
+ Place one exact sibling file beside a template. Only the `.fixtures.ts` suffix is recognized, and it must default-export one props object:
27
+
28
+ ```ts [app/emails/welcome.fixtures.ts]
29
+ import type { EmailComponentProps } from '@lupinum/nuxt-email'
30
+ import type WelcomeEmail from './welcome.vue'
31
+
32
+ export default {
33
+ firstName: 'Ada',
34
+ activationUrl: 'https://example.com/activate',
35
+ } satisfies EmailComponentProps<typeof WelcomeEmail>
36
+ ```
37
+
38
+ The preview intentionally supports **one fixed scenario per template** and does not accept request-provided props or generate editing forms.
39
+
40
+ ## Inspect output
41
+
42
+ <steps>
43
+ ### Preview
44
+
45
+ Loads the raw rendered HTML in a sandboxed iframe (no script permission).
46
+
47
+ ### HTML
48
+
49
+ Displays the exact `html` returned by `renderEmail()`.
50
+
51
+ ### Plain text
52
+
53
+ Displays the exact `text` returned by `renderEmail()`.
54
+
55
+ ### Copy / Open
56
+
57
+ **Copy** copies the active representation; **Open** opens the raw development render in a separate tab.
58
+ </steps>
59
+
60
+ Render failures show the template name, the `EmailRenderError` stack, and its original cause. Use the [error reference](/raw/docs/reference/errors.md) to find the recovery step by symptom.
61
+
62
+ A template without a fixture remains in the list. Add its sibling fixture before trying to render it.
63
+
64
+ ## Mind the approximate Gmail clipping budget
65
+
66
+ Gmail is commonly observed clipping messages around **102 KB**, but behavior can vary with the account, message, and delivery path. Nuxt Email reports the rendered HTML's exact UTF-8 byte count; the Gmail-specific interpretation is an approximate authoring warning, not a guarantee:
67
+
68
+ ```ts
69
+ const { html } = await renderEmail('welcome', props)
70
+ console.log('bytes:', Buffer.byteLength(html, 'utf8'))
71
+ ```
72
+
73
+ Keep the rendered HTML comfortably below the warning area and test the delivered message in your target Gmail accounts. Inlined Tailwind utilities and repeated markup add up quickly, so measure your largest, most dynamic template.
74
+
75
+ The preview has no dark-mode simulation. Email clients transform dark-mode content differently, so test dark rendering in the real clients you support.
76
+
77
+ ## Production boundary
78
+
79
+ The preview page, endpoints, and fixture imports exist **only in development**. Production builds contain none of their routes or fixture data. The raw render endpoint uses a restrictive content security policy. Its iframe has no script permission.
80
+
81
+ The development endpoints cannot become production render or send APIs. Use the typed server-only [`renderEmail`](/raw/docs/reference/render-email.md) in your handlers.
82
+
83
+ Next, [render the template from Nitro](/raw/docs/getting-started/render-from-nitro.md).