@lupinum/nuxt-email 1.0.0-beta.2 → 1.0.0-beta.4
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.
- package/CHANGELOG.md +35 -1
- package/README.md +31 -3
- package/dist/agent/AGENTS.md +47 -0
- package/dist/agent/manifest.json +208 -0
- package/dist/agent/pages/docs/components/code.md +68 -0
- package/dist/agent/pages/docs/components/content.md +111 -0
- package/dist/agent/pages/docs/components/layout.md +89 -0
- package/dist/agent/pages/docs/components/markdown.md +49 -0
- package/dist/agent/pages/docs/components/structure.md +122 -0
- package/dist/agent/pages/docs/components/tailwind.md +41 -0
- package/dist/agent/pages/docs/components.md +56 -0
- package/dist/agent/pages/docs/getting-started/choose-a-tool.md +38 -0
- package/dist/agent/pages/docs/getting-started/first-email.md +99 -0
- package/dist/agent/pages/docs/getting-started/installation.md +97 -0
- package/dist/agent/pages/docs/getting-started/render-from-nitro.md +59 -0
- package/dist/agent/pages/docs/getting-started.md +62 -0
- package/dist/agent/pages/docs/guides/client-compatibility.md +65 -0
- package/dist/agent/pages/docs/guides/plain-text.md +42 -0
- package/dist/agent/pages/docs/guides/preview-workflow.md +83 -0
- package/dist/agent/pages/docs/guides/sending-email.md +132 -0
- package/dist/agent/pages/docs/guides/standalone-rendering.md +118 -0
- package/dist/agent/pages/docs/guides/subject-lines.md +95 -0
- package/dist/agent/pages/docs/guides/tailwind.md +128 -0
- package/dist/agent/pages/docs/guides/testing-your-emails.md +118 -0
- package/dist/agent/pages/docs/guides.md +32 -0
- package/dist/agent/pages/docs/reference/conformance.md +55 -0
- package/dist/agent/pages/docs/reference/define-email.md +69 -0
- package/dist/agent/pages/docs/reference/errors.md +72 -0
- package/dist/agent/pages/docs/reference/migration.md +144 -0
- package/dist/agent/pages/docs/reference/module.md +90 -0
- package/dist/agent/pages/docs/reference/render-email.md +107 -0
- package/dist/agent/pages/docs/reference.md +30 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -3
- package/package.json +15 -9
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Markdown"
|
|
3
|
+
description: "EMarkdown renders Markdown to email-safe HTML with inline styles and per-element overrides."
|
|
4
|
+
url: "https://nuxt-email.lupinum.com/docs/components/markdown"
|
|
5
|
+
route: "/docs/components/markdown"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Markdown
|
|
13
|
+
|
|
14
|
+
> EMarkdown renders Markdown to email-safe HTML with inline styles and per-element overrides.
|
|
15
|
+
|
|
16
|
+
## EMarkdown
|
|
17
|
+
|
|
18
|
+
`EMarkdown` renders Markdown to email-safe HTML with inline styles. It is useful when content comes from a content management system (CMS) rather than Vue markup.
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Default | Required |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| `source` | `string` | Text-only default slot | No |
|
|
23
|
+
| `markdownCustomStyles` | Per-element style map | Built-in email-safe styles | No |
|
|
24
|
+
|
|
25
|
+
When `source` is a string, it takes precedence over the default slot. Custom styles merge over the defaults.
|
|
26
|
+
|
|
27
|
+
Raw HTML is rejected, including HTML comments. Links and images accept relative URLs plus `http`, `https`, `mailto`, `tel`, and `cid`; other schemes are rejected after HTML-entity and control-character normalization. HTML-looking text inside code spans and fences is escaped. This keeps Markdown from becoming an implicit raw-HTML escape hatch.
|
|
28
|
+
|
|
29
|
+
The rendered container accepts Vue's normal `class`, `style`, and safe HTML attributes. Use ordinary string, object, or array `style` values; include CSS units explicitly for dimensional values such as padding.
|
|
30
|
+
|
|
31
|
+
The default slot must contain **text only**; an element child throws a `TypeError` rather than being silently stringified. Links render with `target="_blank"`, and tables render with `role="presentation"`.
|
|
32
|
+
|
|
33
|
+
```vue
|
|
34
|
+
<EMarkdown source="# Title\n\nA paragraph with a [link](https://example.com)." />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
With overrides:
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<EMarkdown
|
|
41
|
+
:source="body"
|
|
42
|
+
:markdown-custom-styles="{ h1: { color: '#0f172a' }, a: { color: '#2563eb' } }"
|
|
43
|
+
:style="{ padding: '0 24px' }"
|
|
44
|
+
/>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
<card title="Divergence: no data-id marker" icon="lucide:git-compare">
|
|
48
|
+
Unlike React Email, the container omits the `data-id="react-email-markdown"` marker (the same no-data-id divergence as `EColumn`). No Vue behavior consumes the marker. See the [conformance report](/raw/docs/reference/conformance.md).
|
|
49
|
+
</card>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Structure"
|
|
3
|
+
description: "EHtml, EHead, EBody, EPreview, and EFont — the document shell, head, body, inbox preview text, and font declarations."
|
|
4
|
+
url: "https://nuxt-email.lupinum.com/docs/components/structure"
|
|
5
|
+
route: "/docs/components/structure"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Structure
|
|
13
|
+
|
|
14
|
+
> EHtml, EHead, EBody, EPreview, and EFont — the document shell, head, body, inbox preview text, and font declarations.
|
|
15
|
+
|
|
16
|
+
These components form the email document shell. Every render must contain exactly one `<html>` root and one `<body>`.
|
|
17
|
+
|
|
18
|
+
## EHtml
|
|
19
|
+
|
|
20
|
+
`EHtml` renders the required document root.
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Required |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `lang` | `string` | — | Yes |
|
|
25
|
+
| `dir` | Safe native `dir` value | `ltr` | No |
|
|
26
|
+
|
|
27
|
+
`lang` must be a non-empty string. `EHtml` also accepts the [shared safe native attributes](/raw/docs/components.md#rules-every-component-shares).
|
|
28
|
+
|
|
29
|
+
```vue
|
|
30
|
+
<EHtml lang="en"><EBody>Content</EBody></EHtml>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## EHead
|
|
34
|
+
|
|
35
|
+
`EHead` renders `<head>`. It inserts the UTF-8 content-type and Apple message-reformatting meta tags before its default slot. Put `<title>`, `EFont`, and other email head content in that slot.
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Default | Required |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| — | No component-specific props; accepts [shared safe native attributes](/raw/docs/components.md#rules-every-component-shares). | — | — |
|
|
40
|
+
|
|
41
|
+
```vue
|
|
42
|
+
<EHead><title>Order confirmed</title></EHead>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## EBody
|
|
46
|
+
|
|
47
|
+
`EBody` renders `<body>` and a full-width presentation table. Author styles apply to the inner cell. Background values mirror to `<body>`, where supplied margin and padding reset properties become zero.
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Required |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| `lang` | Safe native `lang` value | `en` | No |
|
|
52
|
+
| `dir` | Safe native `dir` value | `ltr` | No |
|
|
53
|
+
|
|
54
|
+
`EBody` accepts [shared safe native attributes](/raw/docs/components.md#rules-every-component-shares). Its language and direction do not inherit from `EHtml`; set both wrappers when the email uses another language or right-to-left text.
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<EBody :style="{ backgroundColor: '#f5f5f5' }">Content</EBody>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<EHtml lang="ar" dir="rtl">
|
|
62
|
+
<EHead>
|
|
63
|
+
<title>تم تأكيد الطلب</title>
|
|
64
|
+
</EHead>
|
|
65
|
+
<EBody lang="ar" dir="rtl">
|
|
66
|
+
<EText>تم تأكيد طلبك.</EText>
|
|
67
|
+
</EBody>
|
|
68
|
+
</EHtml>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Localization and right-to-left text
|
|
72
|
+
|
|
73
|
+
Keep localization in application code. Either create one literal template per locale, such as `welcome/en` and `welcome/ar`, or pass already-resolved strings through typed props. Do not call application-context internationalization composables inside an email template. Email templates render in an isolated server application.
|
|
74
|
+
|
|
75
|
+
Set matching `lang` and `dir` values on both `EHtml` and `EBody`, as shown above. The wrappers do not inherit these values from one another.
|
|
76
|
+
|
|
77
|
+
## EPreview
|
|
78
|
+
|
|
79
|
+
`EPreview` renders hidden inbox-preview text from a text-only default slot.
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Default | Required |
|
|
82
|
+
| --- | --- | --- | --- |
|
|
83
|
+
| — | No component-specific props; accepts [shared safe native attributes](/raw/docs/components.md#rules-every-component-shares). | — | — |
|
|
84
|
+
|
|
85
|
+
Preview text is safely truncated at 200 UTF-16 code units. Compatibility filler is added when the text is shorter. Author styles cannot make the preview visible, and the preview is excluded from derived plain text.
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<EPreview>Your order is confirmed.</EPreview>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Use an explicit `<title>` inside `EHead`. `EPreview` does not generate or move a title into the head. This is an [intentional divergence](/raw/docs/reference/conformance.md).
|
|
92
|
+
|
|
93
|
+
## EFont
|
|
94
|
+
|
|
95
|
+
`EFont` declares an `@font-face` and a document-wide default font. Place it inside `EHead`.
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Default | Required |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `fontFamily` | `string` | — | Yes |
|
|
100
|
+
| `fallbackFontFamily` | `FontFallback \| readonly [FontFallback, ...FontFallback[]]` | — | Yes |
|
|
101
|
+
| `webFont` | `{ url: string, format: FontFormat }` | — | No |
|
|
102
|
+
| `fontStyle` | `'normal' \| 'italic' \| 'oblique'` | `normal` | No |
|
|
103
|
+
| `fontWeight` | `number \| 'normal' \| 'bold' \| 'bolder' \| 'lighter'` | `400` | No |
|
|
104
|
+
|
|
105
|
+
Supported fallbacks are `Arial`, `Helvetica`, `Verdana`, `Georgia`, `Times New Roman`, `serif`, `sans-serif`, `monospace`, `cursive`, and `fantasy`. Numeric weights must be from 1 through 1000. Web-font formats are `woff`, `woff2`, `truetype`, `opentype`, `embedded-opentype`, and `svg`.
|
|
106
|
+
|
|
107
|
+
`EFont` emits one `<style>` with the font-face rule and a global font stack. Outlook uses the first fallback through its specific `mso-font-alt` property. Invalid or empty values fail during rendering.
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<EHead>
|
|
111
|
+
<EFont
|
|
112
|
+
font-family="Roboto"
|
|
113
|
+
:fallback-font-family="['Verdana', 'sans-serif']"
|
|
114
|
+
:web-font="{ url: 'https://fonts.example.com/roboto.woff2', format: 'woff2' }"
|
|
115
|
+
:font-weight="700"
|
|
116
|
+
/>
|
|
117
|
+
</EHead>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
<card title="Client note" icon="lucide:mail">
|
|
121
|
+
Only some clients honor web fonts. Choose a fallback stack that resembles the primary font and verify it in the clients you support.
|
|
122
|
+
</card>
|
|
@@ -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,97 @@
|
|
|
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
|
+
### Use a coding agent
|
|
37
|
+
|
|
38
|
+
A coding agent is a development tool that can inspect and change your project.
|
|
39
|
+
After installation, copy this prompt into your coding agent:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Add Nuxt Email to this Nuxt application and render one transactional email from
|
|
43
|
+
a Vue template. Read the project's existing instructions first. Resolve
|
|
44
|
+
@lupinum/nuxt-email/agent-docs from this application's directory and read its
|
|
45
|
+
starting pages. Use the installed version's examples and public types. Preserve
|
|
46
|
+
existing routes, security boundaries, conventions, and AGENTS.md instructions.
|
|
47
|
+
Add or update one short Nuxt Email pointer in AGENTS.md if the project allows
|
|
48
|
+
it; do not duplicate the documentation. If the file is absent, create only that
|
|
49
|
+
pointer. Report missing guidance. Verify the server render, HTML and plain-text
|
|
50
|
+
output, and one invalid request. Keep delivery in application-owned code.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If the installed package has no `agent-docs` export, read its packaged README,
|
|
54
|
+
types, and `CONFORMANCE.md`. Use documentation from the matching source tag when
|
|
55
|
+
more detail is needed. Installing or updating the package does not edit project
|
|
56
|
+
instructions. The pointer resolves the installed package, so upgrades and
|
|
57
|
+
rollbacks select the matching documentation without copying it into your
|
|
58
|
+
application.
|
|
59
|
+
|
|
60
|
+
## 2. Confirm the TypeScript entry point
|
|
61
|
+
|
|
62
|
+
If your app does not already have one, add the ordinary Nuxt `tsconfig.json`:
|
|
63
|
+
|
|
64
|
+
```json [tsconfig.json]
|
|
65
|
+
{
|
|
66
|
+
"extends": "./.nuxt/tsconfig.json"
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Optional code blocks
|
|
71
|
+
|
|
72
|
+
Syntax highlighting is opt-in. Nuxt Email uses the Shiki syntax highlighter. Configure exactly the languages your templates use and one theme:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
export default defineNuxtConfig({
|
|
76
|
+
modules: ['@lupinum/nuxt-email'],
|
|
77
|
+
nuxtEmail: {
|
|
78
|
+
codeBlock: {
|
|
79
|
+
languages: ['typescript'],
|
|
80
|
+
theme: 'github-dark',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Without `codeBlock`, `ECodeBlock` is not registered. Shiki is not loaded during module setup or included in the production server bundle.
|
|
87
|
+
|
|
88
|
+
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.
|
|
89
|
+
|
|
90
|
+
## What the module registers
|
|
91
|
+
|
|
92
|
+
- Server-only auto-imports for eighteen built-in `E*` components, plus `ECodeBlock` when configured.
|
|
93
|
+
- A Nitro-only `renderEmail(name, props)` auto-import, typed from `app/emails/`.
|
|
94
|
+
- The explicit `@lupinum/nuxt-email/define-email` entry point for declaring a subject inside a template.
|
|
95
|
+
- A development-only preview application at `/__email` (never present in production output).
|
|
96
|
+
|
|
97
|
+
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,62 @@
|
|
|
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.9.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
|
+
[`1.0.0-beta.2`](https://www.npmjs.com/package/@lupinum/nuxt-email/v/1.0.0-beta.2) is published on npm's `next` tag. 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.
|