nuxt-generation-emails 1.0.2 → 1.0.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/README.md +10 -10
- package/dist/cli/index.mjs +2 -2
- package/dist/module.d.mts +2 -2
- package/dist/module.d.ts +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -13
- package/dist/runtime/components/ApiTester.vue +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ export default defineNuxtConfig({
|
|
|
46
46
|
Scaffold the emails directory with example templates and reusable components:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
npx nuxt-
|
|
49
|
+
npx nuxt-generation-emails setup
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
This creates:
|
|
@@ -82,9 +82,9 @@ Then add this to your `.vscode/settings.json` so `.mjml` files get Handlebars hi
|
|
|
82
82
|
### 3. Add More Templates
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
npx nuxt-
|
|
86
|
-
npx nuxt-
|
|
87
|
-
npx nuxt-
|
|
85
|
+
npx nuxt-generation-emails add welcome
|
|
86
|
+
npx nuxt-generation-emails add v1/order-confirmation
|
|
87
|
+
npx nuxt-generation-emails add marketing/campaigns/summer-sale
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
Each command creates a `.vue` + `.mjml` pair with a ready-to-customize starter template.
|
|
@@ -289,14 +289,14 @@ The `components/` directory is reserved — it is skipped during route generatio
|
|
|
289
289
|
|
|
290
290
|
## Sending Emails
|
|
291
291
|
|
|
292
|
-
The generated `POST` endpoints render email HTML via MJML. To send, register a Nitro plugin that listens for the `nuxt-
|
|
292
|
+
The generated `POST` endpoints render email HTML via MJML. To send, register a Nitro plugin that listens for the `nuxt-generation-emails:send` hook.
|
|
293
293
|
|
|
294
294
|
### Server plugin
|
|
295
295
|
|
|
296
296
|
```ts
|
|
297
297
|
// server/plugins/gen-emails.ts
|
|
298
298
|
export default defineNitroPlugin((nitro) => {
|
|
299
|
-
nitro.hooks.hook('nuxt-
|
|
299
|
+
nitro.hooks.hook('nuxt-generation-emails:send', async ({ html, data }) => {…})
|
|
300
300
|
// data contains: { to?, from?, subject?, ...anything from sendData }
|
|
301
301
|
console.log('Sending email to:', data.to)
|
|
302
302
|
// Your provider logic here (SendGrid, SES, Postmark, etc.)
|
|
@@ -317,7 +317,7 @@ import sgMail from '@sendgrid/mail'
|
|
|
317
317
|
export default defineNitroPlugin((nitro) => {
|
|
318
318
|
sgMail.setApiKey(process.env.SENDGRID_API_KEY!)
|
|
319
319
|
|
|
320
|
-
nitro.hooks.hook('nuxt-
|
|
320
|
+
nitro.hooks.hook('nuxt-generation-emails:send', async ({ html, data }) => {
|
|
321
321
|
await sgMail.send({
|
|
322
322
|
to: data.to as string,
|
|
323
323
|
from: (data.from as string) || 'noreply@yourdomain.com',
|
|
@@ -352,7 +352,7 @@ curl -X POST http://localhost:3000/api/emails/welcome \
|
|
|
352
352
|
|
|
353
353
|
1. `templateData` from the request body is passed directly to the Handlebars template (no transformations)
|
|
354
354
|
2. MJML compiles the result into email-safe HTML
|
|
355
|
-
3. The `nuxt-
|
|
355
|
+
3. The `nuxt-generation-emails:send` hook is called with `{ html, data }` where `data` is `sendData`
|
|
356
356
|
4. The response always returns `{ success: true, html: "..." }`
|
|
357
357
|
|
|
358
358
|
If no Nitro plugin is configured, the endpoint still renders and returns the HTML — useful for testing.
|
|
@@ -426,8 +426,8 @@ export default defineEventHandler((event) => {
|
|
|
426
426
|
|
|
427
427
|
| Command | Description |
|
|
428
428
|
|---------|-------------|
|
|
429
|
-
| `npx nuxt-
|
|
430
|
-
| `npx nuxt-
|
|
429
|
+
| `npx nuxt-generation-emails setup` | Scaffold the emails directory with components and an example template |
|
|
430
|
+
| `npx nuxt-generation-emails add <name>` | Create a new email template (`.vue` + `.mjml` pair) |
|
|
431
431
|
|
|
432
432
|
---
|
|
433
433
|
|
package/dist/cli/index.mjs
CHANGED
|
@@ -423,8 +423,8 @@ const setupCommand = defineCommand({
|
|
|
423
423
|
|
|
424
424
|
const main = defineCommand({
|
|
425
425
|
meta: {
|
|
426
|
-
name: "nuxt-
|
|
427
|
-
description: "CLI for nuxt-
|
|
426
|
+
name: "nuxt-generation-emails",
|
|
427
|
+
description: "CLI for nuxt-generation-emails module",
|
|
428
428
|
version: "1.0.0"
|
|
429
429
|
},
|
|
430
430
|
subCommands: {
|
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
|
-
/** Payload passed to the `nuxt-
|
|
3
|
+
/** Payload passed to the `nuxt-generation-emails:send` Nitro runtime hook. */
|
|
4
4
|
type NuxtGenEmailsSendData<TAdditional extends Record<string, unknown> = Record<string, unknown>> = {
|
|
5
5
|
/** Recipient email address. */
|
|
6
6
|
to?: string;
|
|
@@ -9,7 +9,7 @@ type NuxtGenEmailsSendData<TAdditional extends Record<string, unknown> = Record<
|
|
|
9
9
|
/** Email subject line. */
|
|
10
10
|
subject?: string;
|
|
11
11
|
} & TAdditional;
|
|
12
|
-
/** Payload passed to the `nuxt-
|
|
12
|
+
/** Payload passed to the `nuxt-generation-emails:send` Nitro runtime hook. */
|
|
13
13
|
interface NuxtGenEmailsSendPayload<TSendData extends Record<string, unknown> = NuxtGenEmailsSendData> {
|
|
14
14
|
/** The rendered HTML string of the email template. */
|
|
15
15
|
html: string;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
|
|
3
|
-
/** Payload passed to the `nuxt-
|
|
3
|
+
/** Payload passed to the `nuxt-generation-emails:send` Nitro runtime hook. */
|
|
4
4
|
type NuxtGenEmailsSendData<TAdditional extends Record<string, unknown> = Record<string, unknown>> = {
|
|
5
5
|
/** Recipient email address. */
|
|
6
6
|
to?: string;
|
|
@@ -9,7 +9,7 @@ type NuxtGenEmailsSendData<TAdditional extends Record<string, unknown> = Record<
|
|
|
9
9
|
/** Email subject line. */
|
|
10
10
|
subject?: string;
|
|
11
11
|
} & TAdditional;
|
|
12
|
-
/** Payload passed to the `nuxt-
|
|
12
|
+
/** Payload passed to the `nuxt-generation-emails:send` Nitro runtime hook. */
|
|
13
13
|
interface NuxtGenEmailsSendPayload<TSendData extends Record<string, unknown> = NuxtGenEmailsSendData> {
|
|
14
14
|
/** The rendered HTML string of the email template. */
|
|
15
15
|
html: string;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -304,7 +304,7 @@ export default defineEventHandler(async (event) => {
|
|
|
304
304
|
|
|
305
305
|
const nitro = useNitroApp()
|
|
306
306
|
// @ts-ignore - custom hook
|
|
307
|
-
await nitro.hooks.callHook('nuxt-
|
|
307
|
+
await nitro.hooks.callHook('nuxt-generation-emails:send', { html, data: sendData })
|
|
308
308
|
|
|
309
309
|
return { success: true, message: 'Email rendered successfully', html }
|
|
310
310
|
}
|
|
@@ -506,7 +506,7 @@ function generateServerRoutes(emailsDir, buildDir) {
|
|
|
506
506
|
const emailPath = `${routePrefix}/${emailName}`.replace(/^\//, "");
|
|
507
507
|
const mjmlPath = join(dirPath, `${emailName}.mjml`);
|
|
508
508
|
if (!fs.existsSync(mjmlPath)) {
|
|
509
|
-
console.warn(`[nuxt-
|
|
509
|
+
console.warn(`[nuxt-generation-emails] Missing co-located MJML file for ${emailName}.vue \u2014 skipping API route. Expected: ${mjmlPath}`);
|
|
510
510
|
continue;
|
|
511
511
|
}
|
|
512
512
|
const handlerDir = routePrefix ? join(handlersDir, routePrefix.replace(/^\//, "")) : handlersDir;
|
|
@@ -519,7 +519,7 @@ function generateServerRoutes(emailsDir, buildDir) {
|
|
|
519
519
|
const handlerFilePath = join(handlerDir, handlerFileName);
|
|
520
520
|
const handlerContent = generateApiRoute(emailName, emailPath, examplePayload);
|
|
521
521
|
fs.writeFileSync(handlerFilePath, handlerContent, "utf-8");
|
|
522
|
-
console.log(`[nuxt-
|
|
522
|
+
console.log(`[nuxt-generation-emails] Generated API handler: ${handlerFilePath}`);
|
|
523
523
|
handlers.push({
|
|
524
524
|
route: `/api/emails/${emailPath}`,
|
|
525
525
|
method: "post",
|
|
@@ -548,7 +548,7 @@ const module$1 = defineNuxtModule({
|
|
|
548
548
|
async setup(options, nuxt) {
|
|
549
549
|
const resolver = createResolver(import.meta.url);
|
|
550
550
|
addTypeTemplate({
|
|
551
|
-
filename: "types/nuxt-
|
|
551
|
+
filename: "types/nuxt-generation-emails-nitro.d.ts",
|
|
552
552
|
getContents: () => `
|
|
553
553
|
export interface NuxtGenEmailsSendPayload {
|
|
554
554
|
html: string
|
|
@@ -571,12 +571,12 @@ export interface NuxtGenEmailsApiBody<
|
|
|
571
571
|
|
|
572
572
|
declare module 'nitropack' {
|
|
573
573
|
interface NitroRuntimeHooks {
|
|
574
|
-
'nuxt-
|
|
574
|
+
'nuxt-generation-emails:send': (payload: NuxtGenEmailsSendPayload) => void | Promise<void>
|
|
575
575
|
}
|
|
576
576
|
}
|
|
577
577
|
declare module 'nitropack/types' {
|
|
578
578
|
interface NitroRuntimeHooks {
|
|
579
|
-
'nuxt-
|
|
579
|
+
'nuxt-generation-emails:send': (payload: NuxtGenEmailsSendPayload) => void | Promise<void>
|
|
580
580
|
}
|
|
581
581
|
}
|
|
582
582
|
`
|
|
@@ -605,10 +605,15 @@ export function registerMjmlComponents(): void {
|
|
|
605
605
|
write: true,
|
|
606
606
|
getContents: () => `import { computed, h, getCurrentInstance } from 'vue'
|
|
607
607
|
import type { ComputedRef } from 'vue'
|
|
608
|
-
import mjml2html from 'mjml-browser'
|
|
609
608
|
import Handlebars from 'handlebars'
|
|
610
609
|
import { registerMjmlComponents } from './register-components'
|
|
611
610
|
|
|
611
|
+
// Lazy-load mjml-browser only on the client to avoid "window is not defined" during SSR
|
|
612
|
+
let mjml2html: ((mjml: string) => { html: string; errors: unknown[] }) | null = null
|
|
613
|
+
if (import.meta.client) {
|
|
614
|
+
mjml2html = (await import('mjml-browser')).default
|
|
615
|
+
}
|
|
616
|
+
|
|
612
617
|
const mjmlTemplates: Record<string, string> = import.meta.glob(
|
|
613
618
|
['${templateGlobPath}/**/*.mjml', '!${templateGlobPath}/components/**'],
|
|
614
619
|
{ query: '?raw', import: 'default', eager: true }
|
|
@@ -644,7 +649,7 @@ export function useNgeTemplate(name: string, props: Record<string, unknown>): Co
|
|
|
644
649
|
const source = templateMap[name]
|
|
645
650
|
if (!source) {
|
|
646
651
|
const available = Object.keys(templateMap).join(', ')
|
|
647
|
-
console.error(\`[nuxt-
|
|
652
|
+
console.error(\`[nuxt-generation-emails] Template "\${name}" not found. Available: \${available}\`)
|
|
648
653
|
const fallback = computed(() => \`<pre style="color:red;">Template "\${name}" not found</pre>\`)
|
|
649
654
|
const instance = getCurrentInstance()
|
|
650
655
|
if (instance) instance.render = () => h('div', { innerHTML: fallback.value })
|
|
@@ -654,6 +659,8 @@ export function useNgeTemplate(name: string, props: Record<string, unknown>): Co
|
|
|
654
659
|
const compiled = Handlebars.compile(source)
|
|
655
660
|
|
|
656
661
|
const renderedHtml = computed(() => {
|
|
662
|
+
// mjml-browser requires window \u2014 skip rendering during SSR
|
|
663
|
+
if (!mjml2html) return ''
|
|
657
664
|
try {
|
|
658
665
|
const mjmlString = compiled({ ...props })
|
|
659
666
|
const result = mjml2html(mjmlString)
|
|
@@ -724,19 +731,19 @@ export function useNgeTemplate(name: string, props: Record<string, unknown>): Co
|
|
|
724
731
|
}
|
|
725
732
|
if (nuxt.options.dev && fs.existsSync(emailsDir)) {
|
|
726
733
|
const relDir = relative(nuxt.options.rootDir, emailsDir);
|
|
727
|
-
consola.info(`[nuxt-
|
|
734
|
+
consola.info(`[nuxt-generation-emails] Watching for new templates in ${relDir}/`);
|
|
728
735
|
nuxt.options.watch.push(emailsDir + "/**/*.vue");
|
|
729
736
|
nuxt.options.watch.push(emailsDir + "/**/*.mjml");
|
|
730
737
|
nuxt.hook("builder:watch", (event, relativePath) => {
|
|
731
738
|
const absolutePath = join(nuxt.options.rootDir, relativePath);
|
|
732
739
|
const rel = relative(emailsDir, absolutePath);
|
|
733
740
|
if (event === "add" && (relativePath.endsWith(".vue") || relativePath.endsWith(".mjml"))) {
|
|
734
|
-
consola.success(`[nuxt-
|
|
735
|
-
consola.info("[nuxt-
|
|
741
|
+
consola.success(`[nuxt-generation-emails] New template detected: ${rel}`);
|
|
742
|
+
consola.info("[nuxt-generation-emails] Restarting to register new routes...");
|
|
736
743
|
nuxt.callHook("restart");
|
|
737
744
|
} else if (event === "unlink" && (relativePath.endsWith(".vue") || relativePath.endsWith(".mjml"))) {
|
|
738
|
-
consola.warn(`[nuxt-
|
|
739
|
-
consola.info("[nuxt-
|
|
745
|
+
consola.warn(`[nuxt-generation-emails] Template removed: ${rel}`);
|
|
746
|
+
consola.info("[nuxt-generation-emails] Restarting to update routes...");
|
|
740
747
|
nuxt.callHook("restart");
|
|
741
748
|
}
|
|
742
749
|
});
|
|
@@ -11,7 +11,7 @@ const responseData = ref(null);
|
|
|
11
11
|
const error = ref("");
|
|
12
12
|
const copySuccess = ref(false);
|
|
13
13
|
const copyHtmlSuccess = ref(false);
|
|
14
|
-
const lastUsedEmail = useCookie("nuxt-
|
|
14
|
+
const lastUsedEmail = useCookie("nuxt-generation-emails-last-email", {
|
|
15
15
|
default: () => "",
|
|
16
16
|
sameSite: "lax"
|
|
17
17
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-generation-emails",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A Nuxt module for authoring, previewing, and sending transactional email templates with MJML and Handlebars.",
|
|
5
5
|
"author": "nullcarry@icloud.com",
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"bin": {
|
|
34
|
-
"nuxt-
|
|
34
|
+
"nuxt-generation-emails": "./dist/cli/index.mjs"
|
|
35
35
|
},
|
|
36
36
|
"main": "./dist/module.mjs",
|
|
37
37
|
"types": "./dist/types.d.mts",
|