@razmatinyan/nuxt-email 0.1.0
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/LICENSE +21 -0
- package/README.md +272 -0
- package/dist/module.d.mts +7 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +190 -0
- package/dist/runtime/server/api/config.get.d.ts +5 -0
- package/dist/runtime/server/api/config.get.js +10 -0
- package/dist/runtime/server/api/devtools.d.ts +2 -0
- package/dist/runtime/server/api/devtools.js +265 -0
- package/dist/runtime/server/api/log.get.d.ts +2 -0
- package/dist/runtime/server/api/log.get.js +5 -0
- package/dist/runtime/server/api/preview.d.ts +2 -0
- package/dist/runtime/server/api/preview.js +12 -0
- package/dist/runtime/server/api/send-test.post.d.ts +2 -0
- package/dist/runtime/server/api/send-test.post.js +33 -0
- package/dist/runtime/server/api/templates.get.d.ts +5 -0
- package/dist/runtime/server/api/templates.get.js +8 -0
- package/dist/runtime/server/composables/useEmail.d.ts +9 -0
- package/dist/runtime/server/composables/useEmail.js +96 -0
- package/dist/runtime/server/utils/dev-log.d.ts +14 -0
- package/dist/runtime/server/utils/dev-log.js +8 -0
- package/dist/runtime/server/utils/email-utils.d.ts +9 -0
- package/dist/runtime/server/utils/email-utils.js +134 -0
- package/dist/runtime/server/utils/providers/console.d.ts +6 -0
- package/dist/runtime/server/utils/providers/console.js +44 -0
- package/dist/runtime/server/utils/providers/fetch.d.ts +1 -0
- package/dist/runtime/server/utils/providers/index.d.ts +9 -0
- package/dist/runtime/server/utils/providers/index.js +33 -0
- package/dist/runtime/server/utils/providers/postmark.d.ts +8 -0
- package/dist/runtime/server/utils/providers/postmark.js +94 -0
- package/dist/runtime/server/utils/providers/resend.d.ts +8 -0
- package/dist/runtime/server/utils/providers/resend.js +73 -0
- package/dist/runtime/server/utils/providers/sendgrid.d.ts +8 -0
- package/dist/runtime/server/utils/providers/sendgrid.js +99 -0
- package/dist/runtime/server/utils/providers/shared.d.ts +7 -0
- package/dist/runtime/server/utils/providers/shared.js +29 -0
- package/dist/runtime/server/utils/providers/smtp.d.ts +8 -0
- package/dist/runtime/server/utils/providers/smtp.js +85 -0
- package/dist/runtime/server/utils/template-renderer.d.ts +5 -0
- package/dist/runtime/server/utils/template-renderer.js +10 -0
- package/dist/runtime/server/utils/templates.d.ts +4 -0
- package/dist/runtime/server/utils/templates.js +17 -0
- package/dist/runtime/templates.d.ts +6 -0
- package/dist/runtime/types/index.d.ts +110 -0
- package/dist/runtime/types/index.js +0 -0
- package/dist/types.d.mts +9 -0
- package/package.json +84 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const $fetch: typeof import('ofetch').$fetch
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EmailProvider, EmailRuntimeConfig } from '../../../types/index.js.js';
|
|
2
|
+
type ProviderName = 'console' | 'resend' | 'sendgrid' | 'postmark' | 'smtp';
|
|
3
|
+
export declare const VALID_PROVIDERS: ProviderName[];
|
|
4
|
+
export declare function createProvider(name: string, config: EmailRuntimeConfig): EmailProvider;
|
|
5
|
+
export { ConsoleProvider } from './console.js.js';
|
|
6
|
+
export { ResendProvider } from './resend.js.js';
|
|
7
|
+
export { SendGridProvider } from './sendgrid.js.js';
|
|
8
|
+
export { PostmarkProvider } from './postmark.js.js';
|
|
9
|
+
export { SmtpProvider } from './smtp.js.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ConsoleProvider } from "./console.js";
|
|
2
|
+
import { ResendProvider } from "./resend.js";
|
|
3
|
+
import { SendGridProvider } from "./sendgrid.js";
|
|
4
|
+
import { PostmarkProvider } from "./postmark.js";
|
|
5
|
+
import { SmtpProvider } from "./smtp.js";
|
|
6
|
+
export const VALID_PROVIDERS = [
|
|
7
|
+
"console",
|
|
8
|
+
"resend",
|
|
9
|
+
"sendgrid",
|
|
10
|
+
"postmark",
|
|
11
|
+
"smtp"
|
|
12
|
+
];
|
|
13
|
+
const factories = {
|
|
14
|
+
console: () => new ConsoleProvider(),
|
|
15
|
+
resend: (config) => new ResendProvider(config),
|
|
16
|
+
sendgrid: (config) => new SendGridProvider(config),
|
|
17
|
+
postmark: (config) => new PostmarkProvider(config),
|
|
18
|
+
smtp: (config) => new SmtpProvider(config)
|
|
19
|
+
};
|
|
20
|
+
export function createProvider(name, config) {
|
|
21
|
+
const factory = factories[name];
|
|
22
|
+
if (!factory) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`[nuxt-email] Unknown provider "${name}". Valid providers: ${VALID_PROVIDERS.join(", ")}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return factory(config);
|
|
28
|
+
}
|
|
29
|
+
export { ConsoleProvider } from "./console.js";
|
|
30
|
+
export { ResendProvider } from "./resend.js";
|
|
31
|
+
export { SendGridProvider } from "./sendgrid.js";
|
|
32
|
+
export { PostmarkProvider } from "./postmark.js";
|
|
33
|
+
export { SmtpProvider } from "./smtp.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmailProvider, EmailRuntimeConfig, NormalizedPayload, EmailResponse } from '../../../types/index.js.js';
|
|
2
|
+
export declare class PostmarkProvider implements EmailProvider {
|
|
3
|
+
name: string;
|
|
4
|
+
private readonly apiKey;
|
|
5
|
+
constructor(config: EmailRuntimeConfig);
|
|
6
|
+
send(payload: NormalizedPayload): Promise<EmailResponse>;
|
|
7
|
+
verify(): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { toBase64, fetchErrorMessage, omitUndefined } from "./shared.js";
|
|
2
|
+
export class PostmarkProvider {
|
|
3
|
+
name = "postmark";
|
|
4
|
+
apiKey;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
if (!config.apiKey) {
|
|
7
|
+
throw new Error("[nuxt-email] PostmarkProvider requires `apiKey`.");
|
|
8
|
+
}
|
|
9
|
+
this.apiKey = config.apiKey;
|
|
10
|
+
}
|
|
11
|
+
async send(payload) {
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
try {
|
|
14
|
+
const headers = payload.headers ? Object.entries(payload.headers).map(([Name, Value]) => ({
|
|
15
|
+
Name,
|
|
16
|
+
Value
|
|
17
|
+
})) : void 0;
|
|
18
|
+
const attachments = payload.attachments?.map(
|
|
19
|
+
(a) => omitUndefined({
|
|
20
|
+
Name: a.filename,
|
|
21
|
+
Content: toBase64(a.content),
|
|
22
|
+
ContentType: a.contentType ?? "application/octet-stream",
|
|
23
|
+
ContentID: a.cid
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
const body = omitUndefined({
|
|
27
|
+
From: payload.from,
|
|
28
|
+
To: payload.to.join(","),
|
|
29
|
+
Cc: payload.cc?.join(","),
|
|
30
|
+
Bcc: payload.bcc?.join(","),
|
|
31
|
+
ReplyTo: payload.replyTo,
|
|
32
|
+
Subject: payload.subject,
|
|
33
|
+
HtmlBody: payload.html,
|
|
34
|
+
TextBody: payload.text,
|
|
35
|
+
Headers: headers,
|
|
36
|
+
Attachments: attachments,
|
|
37
|
+
Metadata: payload.tags,
|
|
38
|
+
MessageStream: "outbound"
|
|
39
|
+
});
|
|
40
|
+
const res = await $fetch("https://api.postmarkapp.com/email", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: {
|
|
43
|
+
"X-Postmark-Server-Token": this.apiKey,
|
|
44
|
+
Accept: "application/json",
|
|
45
|
+
"Content-Type": "application/json"
|
|
46
|
+
},
|
|
47
|
+
body
|
|
48
|
+
});
|
|
49
|
+
if (res.ErrorCode !== 0) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: `[nuxt-email] Postmark error: ${res.Message}`,
|
|
53
|
+
provider: this.name,
|
|
54
|
+
duration: Date.now() - start
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
messageId: res.MessageID,
|
|
60
|
+
provider: this.name,
|
|
61
|
+
duration: Date.now() - start
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const data = error?.data;
|
|
65
|
+
if (data?.ErrorCode !== void 0 && data.ErrorCode !== 0) {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: `[nuxt-email] Postmark error: ${data.Message ?? "Unknown error"}`,
|
|
69
|
+
provider: this.name,
|
|
70
|
+
duration: Date.now() - start
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
error: fetchErrorMessage("Postmark", error),
|
|
76
|
+
provider: this.name,
|
|
77
|
+
duration: Date.now() - start
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async verify() {
|
|
82
|
+
try {
|
|
83
|
+
await $fetch("https://api.postmarkapp.com/server", {
|
|
84
|
+
headers: {
|
|
85
|
+
"X-Postmark-Server-Token": this.apiKey,
|
|
86
|
+
Accept: "application/json"
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return true;
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmailProvider, EmailRuntimeConfig, NormalizedPayload, EmailResponse } from '../../../types/index.js.js';
|
|
2
|
+
export declare class ResendProvider implements EmailProvider {
|
|
3
|
+
name: string;
|
|
4
|
+
private readonly apiKey;
|
|
5
|
+
constructor(config: EmailRuntimeConfig);
|
|
6
|
+
send(payload: NormalizedPayload): Promise<EmailResponse>;
|
|
7
|
+
verify(): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { toBase64, fetchErrorMessage, omitUndefined } from "./shared.js";
|
|
2
|
+
export class ResendProvider {
|
|
3
|
+
name = "resend";
|
|
4
|
+
apiKey;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
if (!config.apiKey) {
|
|
7
|
+
throw new Error("[nuxt-email] ResendProvider requires `apiKey`.");
|
|
8
|
+
}
|
|
9
|
+
this.apiKey = config.apiKey;
|
|
10
|
+
}
|
|
11
|
+
async send(payload) {
|
|
12
|
+
const start = Date.now();
|
|
13
|
+
try {
|
|
14
|
+
const attachments = payload.attachments?.map(
|
|
15
|
+
(a) => omitUndefined({
|
|
16
|
+
filename: a.filename,
|
|
17
|
+
content: toBase64(a.content),
|
|
18
|
+
content_type: a.contentType,
|
|
19
|
+
content_id: a.cid
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
const tags = payload.tags ? Object.entries(payload.tags).map(([name, value]) => ({
|
|
23
|
+
name,
|
|
24
|
+
value
|
|
25
|
+
})) : void 0;
|
|
26
|
+
const body = omitUndefined({
|
|
27
|
+
from: payload.from,
|
|
28
|
+
to: payload.to,
|
|
29
|
+
cc: payload.cc,
|
|
30
|
+
bcc: payload.bcc,
|
|
31
|
+
reply_to: payload.replyTo,
|
|
32
|
+
subject: payload.subject,
|
|
33
|
+
html: payload.html,
|
|
34
|
+
text: payload.text,
|
|
35
|
+
headers: payload.headers,
|
|
36
|
+
attachments,
|
|
37
|
+
tags,
|
|
38
|
+
scheduled_at: payload.scheduledAt?.toISOString()
|
|
39
|
+
});
|
|
40
|
+
const res = await $fetch(
|
|
41
|
+
"https://api.resend.com/emails",
|
|
42
|
+
{
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
45
|
+
body
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
messageId: res.id,
|
|
51
|
+
provider: this.name,
|
|
52
|
+
duration: Date.now() - start
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: fetchErrorMessage("Resend", error),
|
|
58
|
+
provider: this.name,
|
|
59
|
+
duration: Date.now() - start
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async verify() {
|
|
64
|
+
try {
|
|
65
|
+
await $fetch("https://api.resend.com/domains", {
|
|
66
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmailProvider, EmailRuntimeConfig, NormalizedPayload, EmailResponse } from '../../../types/index.js.js';
|
|
2
|
+
export declare class SendGridProvider implements EmailProvider {
|
|
3
|
+
name: string;
|
|
4
|
+
private readonly apiKey;
|
|
5
|
+
constructor(config: EmailRuntimeConfig);
|
|
6
|
+
send(payload: NormalizedPayload): Promise<EmailResponse>;
|
|
7
|
+
verify(): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toBase64,
|
|
3
|
+
parseAddress,
|
|
4
|
+
fetchErrorMessage,
|
|
5
|
+
omitUndefined
|
|
6
|
+
} from "./shared.js";
|
|
7
|
+
export class SendGridProvider {
|
|
8
|
+
name = "sendgrid";
|
|
9
|
+
apiKey;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
if (!config.apiKey) {
|
|
12
|
+
throw new Error("[nuxt-email] SendGridProvider requires `apiKey`.");
|
|
13
|
+
}
|
|
14
|
+
this.apiKey = config.apiKey;
|
|
15
|
+
}
|
|
16
|
+
async send(payload) {
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
const personalization = {
|
|
20
|
+
to: payload.to.map((e) => ({ email: e }))
|
|
21
|
+
};
|
|
22
|
+
if (payload.cc?.length)
|
|
23
|
+
personalization.cc = payload.cc.map((e) => ({ email: e }));
|
|
24
|
+
if (payload.bcc?.length)
|
|
25
|
+
personalization.bcc = payload.bcc.map((e) => ({ email: e }));
|
|
26
|
+
if (payload.scheduledAt) {
|
|
27
|
+
personalization.send_at = Math.floor(
|
|
28
|
+
payload.scheduledAt.getTime() / 1e3
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const content = [];
|
|
32
|
+
if (payload.text)
|
|
33
|
+
content.push({ type: "text/plain", value: payload.text });
|
|
34
|
+
content.push({ type: "text/html", value: payload.html });
|
|
35
|
+
const attachments = payload.attachments?.map(
|
|
36
|
+
(a) => omitUndefined({
|
|
37
|
+
filename: a.filename,
|
|
38
|
+
content: toBase64(a.content),
|
|
39
|
+
type: a.contentType,
|
|
40
|
+
disposition: a.disposition,
|
|
41
|
+
content_id: a.cid
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
const body = omitUndefined({
|
|
45
|
+
personalizations: [personalization],
|
|
46
|
+
from: parseAddress(payload.from),
|
|
47
|
+
reply_to: payload.replyTo ? parseAddress(payload.replyTo) : void 0,
|
|
48
|
+
subject: payload.subject,
|
|
49
|
+
content,
|
|
50
|
+
attachments,
|
|
51
|
+
headers: payload.headers,
|
|
52
|
+
custom_args: payload.tags
|
|
53
|
+
});
|
|
54
|
+
const res = await $fetch.raw(
|
|
55
|
+
"https://api.sendgrid.com/v3/mail/send",
|
|
56
|
+
{
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
60
|
+
"Content-Type": "application/json"
|
|
61
|
+
},
|
|
62
|
+
body
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
if (res.status !== 202) {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: `[nuxt-email] SendGrid API error (${res.status}): unexpected status`,
|
|
69
|
+
provider: this.name,
|
|
70
|
+
duration: Date.now() - start
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const messageId = res.headers.get("x-message-id") ?? void 0;
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
messageId,
|
|
77
|
+
provider: this.name,
|
|
78
|
+
duration: Date.now() - start
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: fetchErrorMessage("SendGrid", error),
|
|
84
|
+
provider: this.name,
|
|
85
|
+
duration: Date.now() - start
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async verify() {
|
|
90
|
+
try {
|
|
91
|
+
await $fetch("https://api.sendgrid.com/v3/scopes", {
|
|
92
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
93
|
+
});
|
|
94
|
+
return true;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function toBase64(content: string | Buffer): string;
|
|
2
|
+
export declare function parseAddress(addr: string): {
|
|
3
|
+
email: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function fetchErrorMessage(provider: string, error: unknown): string;
|
|
7
|
+
export declare function omitUndefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function toBase64(content) {
|
|
2
|
+
if (Buffer.isBuffer(content)) return content.toString("base64");
|
|
3
|
+
return Buffer.from(content).toString("base64");
|
|
4
|
+
}
|
|
5
|
+
export function parseAddress(addr) {
|
|
6
|
+
const match = addr.match(/^(.+?)\s*<([^>]+)>$/);
|
|
7
|
+
if (match) return { name: match[1].trim(), email: match[2].trim() };
|
|
8
|
+
return { email: addr.trim() };
|
|
9
|
+
}
|
|
10
|
+
export function fetchErrorMessage(provider, error) {
|
|
11
|
+
if (error && typeof error === "object") {
|
|
12
|
+
const e = error;
|
|
13
|
+
const status = typeof e.status === "number" ? e.status : void 0;
|
|
14
|
+
const data = e.data;
|
|
15
|
+
const message = typeof data?.message === "string" && data.message || typeof data?.Message === "string" && data.Message || typeof e.message === "string" && e.message || "Unknown error";
|
|
16
|
+
if (status !== void 0) {
|
|
17
|
+
return `[nuxt-email] ${provider} API error (${status}): ${message}`;
|
|
18
|
+
}
|
|
19
|
+
return `[nuxt-email] ${provider} error: ${message}`;
|
|
20
|
+
}
|
|
21
|
+
return `[nuxt-email] ${provider} error: ${String(error)}`;
|
|
22
|
+
}
|
|
23
|
+
export function omitUndefined(obj) {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const key of Object.keys(obj)) {
|
|
26
|
+
if (obj[key] !== void 0) result[key] = obj[key];
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmailProvider, EmailRuntimeConfig, NormalizedPayload, EmailResponse } from '../../../types/index.js.js';
|
|
2
|
+
export declare class SmtpProvider implements EmailProvider {
|
|
3
|
+
name: string;
|
|
4
|
+
private readonly config;
|
|
5
|
+
constructor(config: EmailRuntimeConfig);
|
|
6
|
+
send(payload: NormalizedPayload): Promise<EmailResponse>;
|
|
7
|
+
verify(): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export class SmtpProvider {
|
|
2
|
+
name = "smtp";
|
|
3
|
+
config;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
if (!config.smtpHost) {
|
|
6
|
+
throw new Error("[nuxt-email] SmtpProvider requires `smtp.host`.");
|
|
7
|
+
}
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
async send(payload) {
|
|
11
|
+
const start = Date.now();
|
|
12
|
+
try {
|
|
13
|
+
let nodemailer;
|
|
14
|
+
try {
|
|
15
|
+
nodemailer = await import("nodemailer");
|
|
16
|
+
} catch {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: "[nuxt-email] SMTP requires the optional peer dep `nodemailer`. Run `npm i nodemailer`.",
|
|
20
|
+
provider: this.name,
|
|
21
|
+
duration: Date.now() - start
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const { smtpHost, smtpPort, smtpSecure, smtpUser, smtpPass } = this.config;
|
|
25
|
+
const transporter = nodemailer.createTransport({
|
|
26
|
+
host: smtpHost,
|
|
27
|
+
port: smtpPort,
|
|
28
|
+
secure: smtpSecure || smtpPort === 465,
|
|
29
|
+
auth: smtpUser ? { user: smtpUser, pass: smtpPass } : void 0
|
|
30
|
+
});
|
|
31
|
+
const attachments = payload.attachments?.map((a) => ({
|
|
32
|
+
filename: a.filename,
|
|
33
|
+
content: a.content,
|
|
34
|
+
contentType: a.contentType,
|
|
35
|
+
cid: a.cid
|
|
36
|
+
}));
|
|
37
|
+
const info = await transporter.sendMail({
|
|
38
|
+
from: payload.from,
|
|
39
|
+
to: payload.to,
|
|
40
|
+
cc: payload.cc,
|
|
41
|
+
bcc: payload.bcc,
|
|
42
|
+
replyTo: payload.replyTo,
|
|
43
|
+
subject: payload.subject,
|
|
44
|
+
html: payload.html,
|
|
45
|
+
text: payload.text,
|
|
46
|
+
headers: payload.headers,
|
|
47
|
+
attachments
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
messageId: info.messageId,
|
|
52
|
+
provider: this.name,
|
|
53
|
+
duration: Date.now() - start
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: `[nuxt-email] SMTP error: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
provider: this.name,
|
|
60
|
+
duration: Date.now() - start
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async verify() {
|
|
65
|
+
try {
|
|
66
|
+
let nodemailer;
|
|
67
|
+
try {
|
|
68
|
+
nodemailer = await import("nodemailer");
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const { smtpHost, smtpPort, smtpSecure, smtpUser, smtpPass } = this.config;
|
|
73
|
+
const transporter = nodemailer.createTransport({
|
|
74
|
+
host: smtpHost,
|
|
75
|
+
port: smtpPort,
|
|
76
|
+
secure: smtpSecure || smtpPort === 465,
|
|
77
|
+
auth: smtpUser ? { user: smtpUser, pass: smtpPass } : void 0
|
|
78
|
+
});
|
|
79
|
+
await transporter.verify();
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createSSRApp } from "@vue/runtime-dom";
|
|
2
|
+
import { renderToString } from "@vue/server-renderer";
|
|
3
|
+
import juice from "juice";
|
|
4
|
+
export async function renderEmailTemplate(component, props = {}) {
|
|
5
|
+
const app = createSSRApp(component, props);
|
|
6
|
+
const rendered = await renderToString(app);
|
|
7
|
+
const html = juice(rendered);
|
|
8
|
+
const text = rendered.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9
|
+
return { html, text };
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { templates, previewProps } from "#nuxt-email/templates";
|
|
2
|
+
export function getEmailTemplate(name) {
|
|
3
|
+
const component = templates[name];
|
|
4
|
+
if (!component) {
|
|
5
|
+
const available = Object.keys(templates).join(", ") || "none";
|
|
6
|
+
throw new Error(
|
|
7
|
+
`[nuxt-email] Template "${name}" not found. Available: ${available}`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return component;
|
|
11
|
+
}
|
|
12
|
+
export function getPreviewProps(name) {
|
|
13
|
+
return previewProps[name] ?? {};
|
|
14
|
+
}
|
|
15
|
+
export function listTemplates() {
|
|
16
|
+
return Object.keys(templates);
|
|
17
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export interface EmailModuleOptions {
|
|
2
|
+
/** Email provider. Default: 'console' (logs to terminal) */
|
|
3
|
+
provider: 'resend' | 'sendgrid' | 'postmark' | 'smtp' | 'console';
|
|
4
|
+
/** Default sender address: "Name <email>" or "email" */
|
|
5
|
+
from?: string;
|
|
6
|
+
/** Provider API key (prefer env vars via runtimeConfig) */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
smtp?: SmtpConfig;
|
|
9
|
+
/** Per-provider credentials. Each entry overrides the top-level `apiKey`/`smtp`/`from` for that provider. */
|
|
10
|
+
providers?: Partial<Record<EmailModuleOptions['provider'], ProviderOptions>>;
|
|
11
|
+
/** Directory for Vue email templates relative to project root. Default: 'server/emails' */
|
|
12
|
+
templateDir?: string;
|
|
13
|
+
/** Enable preview route in dev mode. Default: true */
|
|
14
|
+
preview?: boolean;
|
|
15
|
+
/** Number of retry attempts on transient failures. Default: 2 */
|
|
16
|
+
retries?: number;
|
|
17
|
+
/** Delay between retries in ms. Default: 1000 */
|
|
18
|
+
retryDelay?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface SmtpConfig {
|
|
21
|
+
host: string;
|
|
22
|
+
port?: number;
|
|
23
|
+
user?: string;
|
|
24
|
+
pass?: string;
|
|
25
|
+
secure?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Credentials for a single provider, overriding the top-level defaults. */
|
|
28
|
+
export interface ProviderOptions {
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
from?: string;
|
|
31
|
+
smtp?: SmtpConfig;
|
|
32
|
+
}
|
|
33
|
+
/** Flat per-provider overrides stored in runtime config. */
|
|
34
|
+
export interface ProviderRuntimeOptions {
|
|
35
|
+
apiKey?: string;
|
|
36
|
+
from?: string;
|
|
37
|
+
smtpHost?: string;
|
|
38
|
+
smtpPort?: number;
|
|
39
|
+
smtpUser?: string;
|
|
40
|
+
smtpPass?: string;
|
|
41
|
+
smtpSecure?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Stored in runtimeConfig._email. Private, never sent to the client. */
|
|
44
|
+
export interface EmailRuntimeConfig {
|
|
45
|
+
provider: string;
|
|
46
|
+
apiKey: string;
|
|
47
|
+
from: string;
|
|
48
|
+
smtpHost: string;
|
|
49
|
+
smtpPort: number;
|
|
50
|
+
smtpUser: string;
|
|
51
|
+
smtpPass: string;
|
|
52
|
+
smtpSecure: boolean;
|
|
53
|
+
retries: number;
|
|
54
|
+
retryDelay: number;
|
|
55
|
+
providers?: Record<string, ProviderRuntimeOptions>;
|
|
56
|
+
}
|
|
57
|
+
export interface EmailPayload {
|
|
58
|
+
to: string | string[];
|
|
59
|
+
subject: string;
|
|
60
|
+
/** Mutually exclusive with `template` */
|
|
61
|
+
html?: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
template?: string;
|
|
64
|
+
props?: Record<string, unknown>;
|
|
65
|
+
from?: string;
|
|
66
|
+
cc?: string | string[];
|
|
67
|
+
bcc?: string | string[];
|
|
68
|
+
replyTo?: string;
|
|
69
|
+
attachments?: EmailAttachment[];
|
|
70
|
+
headers?: Record<string, string>;
|
|
71
|
+
/** Provider must support scheduled sends */
|
|
72
|
+
scheduledAt?: Date;
|
|
73
|
+
/** Provider-dependent */
|
|
74
|
+
tags?: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
export interface EmailAttachment {
|
|
77
|
+
filename: string;
|
|
78
|
+
content: string | Buffer;
|
|
79
|
+
contentType?: string;
|
|
80
|
+
/** 'attachment' | 'inline' */
|
|
81
|
+
disposition?: string;
|
|
82
|
+
/** Content-ID for inline images */
|
|
83
|
+
cid?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface EmailResponse {
|
|
86
|
+
success: boolean;
|
|
87
|
+
messageId?: string;
|
|
88
|
+
error?: string;
|
|
89
|
+
provider: string;
|
|
90
|
+
duration: number;
|
|
91
|
+
}
|
|
92
|
+
export interface EmailProvider {
|
|
93
|
+
name: string;
|
|
94
|
+
send(payload: NormalizedPayload): Promise<EmailResponse>;
|
|
95
|
+
verify?(): Promise<boolean>;
|
|
96
|
+
}
|
|
97
|
+
export interface NormalizedPayload {
|
|
98
|
+
from: string;
|
|
99
|
+
to: string[];
|
|
100
|
+
subject: string;
|
|
101
|
+
html: string;
|
|
102
|
+
text?: string;
|
|
103
|
+
cc?: string[];
|
|
104
|
+
bcc?: string[];
|
|
105
|
+
replyTo?: string;
|
|
106
|
+
attachments?: EmailAttachment[];
|
|
107
|
+
headers?: Record<string, string>;
|
|
108
|
+
scheduledAt?: Date;
|
|
109
|
+
tags?: Record<string, string>;
|
|
110
|
+
}
|
|
File without changes
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NuxtModule } from '@nuxt/schema'
|
|
2
|
+
|
|
3
|
+
import type { default as Module } from './module.mjs'
|
|
4
|
+
|
|
5
|
+
export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
|
|
6
|
+
|
|
7
|
+
export { type EmailAttachment, type EmailModuleOptions, type EmailPayload, type EmailProvider, type EmailResponse, type NormalizedPayload } from '../dist/runtime/types/index.js'
|
|
8
|
+
|
|
9
|
+
export { default } from './module.mjs'
|