@nebutra/email 0.1.0 → 0.1.1

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.
@@ -0,0 +1,17 @@
1
+
2
+ > @nebutra/email@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/integrations/email
3
+ > tsup
4
+
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Using tsup config: /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/integrations/email/tsup.config.ts
9
+ CLI Target: es2022
10
+ CLI Cleaning output folder
11
+ ESM Build start
12
+ ESM dist/index.js 25.96 KB
13
+ ESM dist/index.js.map 50.59 KB
14
+ ESM ⚡️ Build success in 92ms
15
+ DTS Build start
16
+ DTS ⚡️ Build success in 17562ms
17
+ DTS dist/index.d.ts 13.33 KB
@@ -0,0 +1,16 @@
1
+
2
+ > @nebutra/email@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/integrations/email
3
+ > vitest run
4
+
5
+
6
+  RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/integrations/email
7
+
8
+ ✓ src/__tests__/provider.test.ts (10 tests) 254ms
9
+ ✓ src/__tests__/email-contract.test.ts (2 tests) 42ms
10
+ ✓ src/templates/__tests__/templates.test.ts (13 tests) 64ms
11
+
12
+  Test Files  3 passed (3)
13
+  Tests  25 passed (25)
14
+  Start at  08:14:48
15
+  Duration  1.67s (transform 1.61s, setup 0ms, import 2.06s, tests 359ms, environment 1ms)
16
+
@@ -0,0 +1,4 @@
1
+
2
+ > @nebutra/email@0.1.1 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/integrations/email
3
+ > tsc --noEmit
4
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @nebutra/email
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Publish registry package metadata under the MIT license.
@@ -0,0 +1,394 @@
1
+ /**
2
+ * @nebutra/email — Provider abstraction
3
+ *
4
+ * Supports multiple email providers via auto-detection:
5
+ * 1. EMAIL_PROVIDER env var (explicit)
6
+ * 2. RESEND_API_KEY → Resend
7
+ * 3. SMTP_HOST → Nodemailer
8
+ * 4. Fallback → Console (dev/test)
9
+ */
10
+ interface SendOptions {
11
+ to: string | string[];
12
+ subject: string;
13
+ html: string;
14
+ from: string;
15
+ replyTo?: string;
16
+ tags?: {
17
+ name: string;
18
+ value: string;
19
+ }[];
20
+ }
21
+ interface SendResult {
22
+ id: string;
23
+ }
24
+ interface EmailProvider {
25
+ readonly name: string;
26
+ send(opts: SendOptions): Promise<SendResult>;
27
+ }
28
+ type EmailProviderType = "resend" | "nodemailer" | "console";
29
+ declare function getEmailProvider(): EmailProvider;
30
+ declare function resetEmailProvider(): void;
31
+
32
+ /**
33
+ * Console email provider — prints email details to stdout.
34
+ * Perfect for local development and testing without API keys.
35
+ */
36
+
37
+ declare class ConsoleEmailProvider implements EmailProvider {
38
+ readonly name: "console";
39
+ send(opts: SendOptions): Promise<SendResult>;
40
+ }
41
+
42
+ /**
43
+ * Nodemailer email provider — SMTP-based, self-hosted fallback.
44
+ * Requires SMTP_HOST (+ optional SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE).
45
+ */
46
+
47
+ interface NodemailerConfig {
48
+ host: string;
49
+ port: number;
50
+ secure: boolean;
51
+ auth?: {
52
+ user: string;
53
+ pass: string;
54
+ } | undefined;
55
+ }
56
+ declare class NodemailerEmailProvider implements EmailProvider {
57
+ readonly name: "nodemailer";
58
+ private config;
59
+ private transporter;
60
+ constructor(config: NodemailerConfig);
61
+ private getTransporter;
62
+ send(opts: SendOptions): Promise<SendResult>;
63
+ }
64
+
65
+ /**
66
+ * Resend email provider — production-grade transactional email.
67
+ * Requires RESEND_API_KEY environment variable.
68
+ */
69
+
70
+ declare class ResendEmailProvider implements EmailProvider {
71
+ readonly name: "resend";
72
+ private client;
73
+ constructor(apiKey: string);
74
+ send(opts: SendOptions): Promise<SendResult>;
75
+ }
76
+
77
+ /**
78
+ * ReceiptEmail — payment confirmation with billing period and invoice download.
79
+ *
80
+ * react-email is not installed; this module exposes the same
81
+ * `{ subject, preview, render }` contract using a plain template literal so the
82
+ * catalog can be migrated to React Email later without changing call sites.
83
+ */
84
+ interface ReceiptEmailProps {
85
+ customerName: string;
86
+ invoiceNumber: string;
87
+ amount: string;
88
+ currency: string;
89
+ periodStart: string;
90
+ periodEnd: string;
91
+ downloadUrl: string;
92
+ brandName: string;
93
+ }
94
+ declare function subject$3(props: ReceiptEmailProps): string;
95
+ declare function preview$3(props: ReceiptEmailProps): string;
96
+ declare function render$3(props: ReceiptEmailProps): string;
97
+ declare const receiptEmail: {
98
+ readonly subject: typeof subject$3;
99
+ readonly preview: typeof preview$3;
100
+ readonly render: typeof render$3;
101
+ };
102
+
103
+ /**
104
+ * InvitationEmail — workspace member invitation with role and expiry.
105
+ *
106
+ * react-email is not installed; this module exposes the same
107
+ * `{ subject, preview, render }` contract using a plain template literal so the
108
+ * catalog can be migrated to React Email later without changing call sites.
109
+ */
110
+ interface InvitationEmailProps {
111
+ inviterName: string;
112
+ organizationName: string;
113
+ role: string;
114
+ acceptUrl: string;
115
+ expiresAt: string;
116
+ brandName: string;
117
+ }
118
+ declare function subject$2(props: InvitationEmailProps): string;
119
+ declare function preview$2(props: InvitationEmailProps): string;
120
+ declare function render$2(props: InvitationEmailProps): string;
121
+ declare const invitationEmail: {
122
+ readonly subject: typeof subject$2;
123
+ readonly preview: typeof preview$2;
124
+ readonly render: typeof render$2;
125
+ };
126
+
127
+ /**
128
+ * PasswordResetEmail — secure password reset link with expiry.
129
+ *
130
+ * react-email is not installed; this module exposes the same
131
+ * `{ subject, preview, render }` contract using a plain template literal so the
132
+ * catalog can be migrated to React Email later without changing call sites.
133
+ */
134
+ interface PasswordResetEmailProps {
135
+ userName: string;
136
+ resetUrl: string;
137
+ expiresInMinutes: number;
138
+ brandName: string;
139
+ }
140
+ declare function subject$1(props: PasswordResetEmailProps): string;
141
+ declare function preview$1(props: PasswordResetEmailProps): string;
142
+ declare function render$1(props: PasswordResetEmailProps): string;
143
+ declare const passwordResetEmail: {
144
+ readonly subject: typeof subject$1;
145
+ readonly preview: typeof preview$1;
146
+ readonly render: typeof render$1;
147
+ };
148
+
149
+ /**
150
+ * WelcomeEmail — onboarding welcome message for new accounts.
151
+ *
152
+ * react-email is not installed; this module exposes the same
153
+ * `{ subject, preview, render }` contract using a plain template literal so the
154
+ * catalog can be migrated to React Email later without changing call sites.
155
+ */
156
+ interface WelcomeEmailProps {
157
+ userName: string;
158
+ loginUrl: string;
159
+ brandName: string;
160
+ }
161
+ declare function subject(props: WelcomeEmailProps): string;
162
+ declare function preview(_props: WelcomeEmailProps): string;
163
+ declare function render(props: WelcomeEmailProps): string;
164
+ declare const welcomeEmail: {
165
+ readonly subject: typeof subject;
166
+ readonly preview: typeof preview;
167
+ readonly render: typeof render;
168
+ };
169
+
170
+ /**
171
+ * Shared HTML layout for the React Email-style template catalog.
172
+ *
173
+ * NOTE: react-email is NOT installed in this workspace. This module ships a
174
+ * plain template literal layout instead so the templates remain framework-free
175
+ * and can be rendered without a React runtime. The contract still mirrors a
176
+ * future React Email migration: each template module exports
177
+ * `{ subject, preview, render }` and the renderer returns a complete HTML
178
+ * document.
179
+ */
180
+ interface RenderedEmail {
181
+ html: string;
182
+ subject: string;
183
+ preview: string;
184
+ }
185
+
186
+ /**
187
+ * Stable shape of a registered template entry for the React Email-style
188
+ * catalog. Distinct from `EMAIL_TEMPLATE_CATALOG` in `../index.ts` which tracks
189
+ * the legacy `send*Email` helper surface.
190
+ */
191
+ interface ReactEmailTemplate<P> {
192
+ id: string;
193
+ label: string;
194
+ description: string;
195
+ fileName: string;
196
+ subject: (props: P) => string;
197
+ preview: (props: P) => string;
198
+ render: (props: P) => string;
199
+ }
200
+ declare const REACT_EMAIL_TEMPLATES: {
201
+ readonly welcome: {
202
+ readonly subject: typeof subject;
203
+ readonly preview: typeof preview;
204
+ readonly render: typeof render;
205
+ readonly id: "welcome-react";
206
+ readonly label: "Welcome (React Email)";
207
+ readonly description: "Account onboarding welcome message";
208
+ readonly fileName: "welcome-react-email.html";
209
+ };
210
+ readonly passwordReset: {
211
+ readonly subject: typeof subject$1;
212
+ readonly preview: typeof preview$1;
213
+ readonly render: typeof render$1;
214
+ readonly id: "password-reset";
215
+ readonly label: "Password Reset";
216
+ readonly description: "Secure password reset link with expiry";
217
+ readonly fileName: "password-reset-email.html";
218
+ };
219
+ readonly invitation: {
220
+ readonly subject: typeof subject$2;
221
+ readonly preview: typeof preview$2;
222
+ readonly render: typeof render$2;
223
+ readonly id: "invitation";
224
+ readonly label: "Invitation";
225
+ readonly description: "Workspace invitation with role and expiry";
226
+ readonly fileName: "invitation-email.html";
227
+ };
228
+ readonly receipt: {
229
+ readonly subject: typeof subject$3;
230
+ readonly preview: typeof preview$3;
231
+ readonly render: typeof render$3;
232
+ readonly id: "receipt";
233
+ readonly label: "Receipt";
234
+ readonly description: "Payment receipt with billing period and PDF link";
235
+ readonly fileName: "receipt-email.html";
236
+ };
237
+ };
238
+
239
+ /**
240
+ * @nebutra/email — Multi-provider transactional email
241
+ *
242
+ * Provider-agnostic email system with auto-detection:
243
+ * 1. EMAIL_PROVIDER env var (explicit: "resend" | "nodemailer" | "console")
244
+ * 2. RESEND_API_KEY present → Resend
245
+ * 3. SMTP_HOST present → Nodemailer (SMTP)
246
+ * 4. Fallback → Console (dev/test, no API key needed)
247
+ *
248
+ * Usage:
249
+ * import { sendWelcomeEmail, sendApiKeyCreatedEmail } from "@nebutra/email";
250
+ * await sendWelcomeEmail({ to: "user@example.com", orgName: "Acme Corp" });
251
+ *
252
+ * Environment variables:
253
+ * EMAIL_PROVIDER — explicit provider override (optional)
254
+ * EMAIL_FROM — verified sender (e.g. "Nebutra <noreply@nebutra.ai>")
255
+ * RESEND_API_KEY — for Resend provider
256
+ * SMTP_HOST — for Nodemailer provider (+ SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE)
257
+ */
258
+
259
+ interface EmailTemplateCatalogEntry {
260
+ id: string;
261
+ label: string;
262
+ description: string;
263
+ sendHelper: string;
264
+ fileName: string;
265
+ }
266
+ declare const EMAIL_TEMPLATE_CATALOG: readonly [{
267
+ readonly id: "welcome";
268
+ readonly label: "Welcome";
269
+ readonly description: "Workspace provisioning welcome email";
270
+ readonly sendHelper: "sendWelcomeEmail";
271
+ readonly fileName: "welcome-email.html";
272
+ }, {
273
+ readonly id: "order-confirmation";
274
+ readonly label: "Order Confirmation";
275
+ readonly description: "Commerce order receipt with line items";
276
+ readonly sendHelper: "sendOrderConfirmationEmail";
277
+ readonly fileName: "order-confirmation-email.html";
278
+ }, {
279
+ readonly id: "magic-link";
280
+ readonly label: "Magic Link";
281
+ readonly description: "Passwordless sign-in link";
282
+ readonly sendHelper: "sendMagicLinkEmail";
283
+ readonly fileName: "magic-link-email.html";
284
+ }, {
285
+ readonly id: "contact-form-received";
286
+ readonly label: "Contact Form Received";
287
+ readonly description: "Contact form acknowledgement";
288
+ readonly sendHelper: "sendContactFormReceivedEmail";
289
+ readonly fileName: "contact-form-received-email.html";
290
+ }, {
291
+ readonly id: "license-created";
292
+ readonly label: "License Created";
293
+ readonly description: "License key delivery email";
294
+ readonly sendHelper: "sendLicenseCreatedEmail";
295
+ readonly fileName: "license-created-email.html";
296
+ }, {
297
+ readonly id: "welcome-react";
298
+ readonly label: "Welcome (React Email)";
299
+ readonly description: "Account onboarding welcome message";
300
+ readonly sendHelper: "sendWelcomeReactEmail";
301
+ readonly fileName: "welcome-react-email.html";
302
+ }, {
303
+ readonly id: "password-reset";
304
+ readonly label: "Password Reset";
305
+ readonly description: "Secure password reset link with expiry";
306
+ readonly sendHelper: "sendPasswordResetEmail";
307
+ readonly fileName: "password-reset-email.html";
308
+ }, {
309
+ readonly id: "invitation";
310
+ readonly label: "Invitation";
311
+ readonly description: "Workspace invitation with role and expiry";
312
+ readonly sendHelper: "sendInvitationEmail";
313
+ readonly fileName: "invitation-email.html";
314
+ }, {
315
+ readonly id: "receipt";
316
+ readonly label: "Receipt";
317
+ readonly description: "Payment receipt with billing period and PDF link";
318
+ readonly sendHelper: "sendReceiptEmail";
319
+ readonly fileName: "receipt-email.html";
320
+ }];
321
+ /**
322
+ * Welcome email sent when a new organization is provisioned.
323
+ */
324
+ declare function sendWelcomeEmail(opts: {
325
+ to: string;
326
+ firstName: string;
327
+ orgName: string;
328
+ dashboardUrl?: string;
329
+ }): Promise<SendResult>;
330
+ /**
331
+ * Order confirmation email sent when an order successfully completes.
332
+ */
333
+ declare function sendOrderConfirmationEmail(opts: {
334
+ to: string;
335
+ orderId: string;
336
+ totalAmount: number;
337
+ items: Array<{
338
+ productId: string;
339
+ quantity: number | string;
340
+ }>;
341
+ }): Promise<SendResult>;
342
+ /**
343
+ * Shipfast-style Magic Link Email for 1-click Authentication.
344
+ */
345
+ declare function sendMagicLinkEmail(opts: {
346
+ to: string;
347
+ magicLinkUrl: string;
348
+ }): Promise<SendResult>;
349
+ /**
350
+ * Contact Form Receipt (Sent to the User)
351
+ */
352
+ declare function sendContactFormReceivedEmail(opts: {
353
+ to: string;
354
+ name: string;
355
+ subject: string;
356
+ }): Promise<SendResult>;
357
+ /**
358
+ * License created email sent when a user claims their OPC/STARTUP license.
359
+ */
360
+ declare function sendLicenseCreatedEmail(opts: {
361
+ to: string;
362
+ firstName: string;
363
+ licenseKey: string;
364
+ tier: string;
365
+ }): Promise<SendResult>;
366
+
367
+ /**
368
+ * Send the React Email-style welcome message. Mirrors `WelcomeEmailProps`
369
+ * exactly so callers can compose messages without depending on the legacy
370
+ * `sendWelcomeEmail` signature.
371
+ */
372
+ declare function sendWelcomeReactEmail(opts: {
373
+ to: string;
374
+ } & WelcomeEmailProps): Promise<SendResult>;
375
+ /**
376
+ * Send a password reset email with a tokenized link and expiry note.
377
+ */
378
+ declare function sendPasswordResetEmail(opts: {
379
+ to: string;
380
+ } & PasswordResetEmailProps): Promise<SendResult>;
381
+ /**
382
+ * Send a workspace invitation with role and accept link.
383
+ */
384
+ declare function sendInvitationEmail(opts: {
385
+ to: string;
386
+ } & InvitationEmailProps): Promise<SendResult>;
387
+ /**
388
+ * Send a payment receipt with billing period and PDF download URL.
389
+ */
390
+ declare function sendReceiptEmail(opts: {
391
+ to: string;
392
+ } & ReceiptEmailProps): Promise<SendResult>;
393
+
394
+ export { ConsoleEmailProvider, EMAIL_TEMPLATE_CATALOG, type EmailProvider, type EmailProviderType, type EmailTemplateCatalogEntry, type InvitationEmailProps, NodemailerEmailProvider, type PasswordResetEmailProps, REACT_EMAIL_TEMPLATES, type ReactEmailTemplate, type ReceiptEmailProps, type RenderedEmail, ResendEmailProvider, type SendOptions, type SendResult, type WelcomeEmailProps, getEmailProvider, invitationEmail, passwordResetEmail, receiptEmail, render$2 as renderInvitationEmail, render$1 as renderPasswordResetEmail, render$3 as renderReceiptEmail, render as renderWelcomeEmail, resetEmailProvider, sendContactFormReceivedEmail, sendInvitationEmail, sendLicenseCreatedEmail, sendMagicLinkEmail, sendOrderConfirmationEmail, sendPasswordResetEmail, sendReceiptEmail, sendWelcomeEmail, sendWelcomeReactEmail, welcomeEmail };
package/dist/index.js ADDED
@@ -0,0 +1,692 @@
1
+ // src/providers/console.ts
2
+ var counter = 0;
3
+ var ConsoleEmailProvider = class {
4
+ name = "console";
5
+ async send(opts) {
6
+ counter += 1;
7
+ const id = `console_${Date.now()}_${counter}`;
8
+ const recipients = Array.isArray(opts.to) ? opts.to.join(", ") : opts.to;
9
+ const separator = "\u2500".repeat(60);
10
+ const lines = [
11
+ `
12
+ ${separator}`,
13
+ `\u{1F4E7} Email (console provider)`,
14
+ `${separator}`,
15
+ `From: ${opts.from}`,
16
+ `To: ${recipients}`,
17
+ `Subject: ${opts.subject}`
18
+ ];
19
+ if (opts.replyTo) {
20
+ lines.push(`Reply-To: ${opts.replyTo}`);
21
+ }
22
+ if (opts.tags?.length) {
23
+ const tagStr = opts.tags.map((t) => `${t.name}=${t.value}`).join(", ");
24
+ lines.push(`Tags: ${tagStr}`);
25
+ }
26
+ lines.push(`ID: ${id}`);
27
+ lines.push(`${separator}
28
+ `);
29
+ process.stdout.write(lines.join("\n"));
30
+ return { id };
31
+ }
32
+ };
33
+
34
+ // src/providers/nodemailer.ts
35
+ var NodemailerEmailProvider = class {
36
+ name = "nodemailer";
37
+ config;
38
+ transporter;
39
+ constructor(config) {
40
+ if (!config.host) {
41
+ throw new Error("SMTP_HOST is required for Nodemailer provider");
42
+ }
43
+ this.config = config;
44
+ }
45
+ async getTransporter() {
46
+ if (this.transporter) return this.transporter;
47
+ const nodemailer = await import("nodemailer");
48
+ this.transporter = nodemailer.createTransport({
49
+ host: this.config.host,
50
+ port: this.config.port,
51
+ secure: this.config.secure,
52
+ ...this.config.auth ? { auth: this.config.auth } : {}
53
+ });
54
+ return this.transporter;
55
+ }
56
+ async send(opts) {
57
+ const transporter = await this.getTransporter();
58
+ const info = await transporter.sendMail({
59
+ from: opts.from,
60
+ to: Array.isArray(opts.to) ? opts.to.join(", ") : opts.to,
61
+ subject: opts.subject,
62
+ html: opts.html,
63
+ ...opts.replyTo ? { replyTo: opts.replyTo } : {},
64
+ ...opts.tags?.length ? {
65
+ headers: {
66
+ "X-Email-Tags": opts.tags.map((t) => `${t.name}=${t.value}`).join(", ")
67
+ }
68
+ } : {}
69
+ });
70
+ return { id: info.messageId };
71
+ }
72
+ };
73
+
74
+ // src/providers/resend.ts
75
+ import { Resend } from "resend";
76
+ var ResendEmailProvider = class {
77
+ name = "resend";
78
+ client;
79
+ constructor(apiKey) {
80
+ if (!apiKey) {
81
+ throw new Error("RESEND_API_KEY is required for Resend provider");
82
+ }
83
+ this.client = new Resend(apiKey);
84
+ }
85
+ async send(opts) {
86
+ const { data, error } = await this.client.emails.send({
87
+ from: opts.from,
88
+ to: Array.isArray(opts.to) ? opts.to : [opts.to],
89
+ subject: opts.subject,
90
+ html: opts.html,
91
+ ...opts.replyTo ? { replyTo: opts.replyTo } : {},
92
+ ...opts.tags ? { tags: opts.tags } : {}
93
+ });
94
+ if (error) {
95
+ throw new Error(`Resend send failed: ${error.message}`);
96
+ }
97
+ return { id: data?.id ?? "unknown" };
98
+ }
99
+ };
100
+
101
+ // src/provider.ts
102
+ var cachedProvider;
103
+ function detectProviderType() {
104
+ const explicit = process.env.EMAIL_PROVIDER;
105
+ if (explicit === "resend" || explicit === "nodemailer" || explicit === "console") {
106
+ return explicit;
107
+ }
108
+ if (process.env.RESEND_API_KEY) return "resend";
109
+ if (process.env.SMTP_HOST) return "nodemailer";
110
+ return "console";
111
+ }
112
+ function createProvider(type) {
113
+ switch (type) {
114
+ case "resend":
115
+ return new ResendEmailProvider(process.env.RESEND_API_KEY);
116
+ case "nodemailer":
117
+ return new NodemailerEmailProvider({
118
+ host: process.env.SMTP_HOST,
119
+ port: Number(process.env.SMTP_PORT ?? "587"),
120
+ secure: process.env.SMTP_SECURE === "true",
121
+ auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS ?? "" } : void 0
122
+ });
123
+ case "console":
124
+ return new ConsoleEmailProvider();
125
+ }
126
+ }
127
+ function getEmailProvider() {
128
+ if (cachedProvider) return cachedProvider;
129
+ const type = detectProviderType();
130
+ cachedProvider = createProvider(type);
131
+ return cachedProvider;
132
+ }
133
+ function resetEmailProvider() {
134
+ cachedProvider = void 0;
135
+ }
136
+
137
+ // src/templates/_layout.ts
138
+ var BRAND_GRADIENT = "linear-gradient(135deg,#0033FE,#0BF1C3)";
139
+ var COLOR_BG = "#f8fafc";
140
+ var COLOR_BORDER = "#e2e8f0";
141
+ var COLOR_HEADING = "#0f172a";
142
+ var COLOR_BODY = "#475569";
143
+ var COLOR_MUTED = "#94a3b8";
144
+ function baseLayout({ brandName, preview: preview5, body }) {
145
+ return `<!DOCTYPE html>
146
+ <html lang="en">
147
+ <head>
148
+ <meta charset="UTF-8" />
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
150
+ <meta name="x-apple-disable-message-reformatting" />
151
+ <title>${escapeHtml(brandName)}</title>
152
+ </head>
153
+ <body style="margin:0;padding:0;background:${COLOR_BG};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
154
+ <div style="display:none;max-height:0;overflow:hidden;">${escapeHtml(preview5)}&nbsp;&zwnj;&nbsp;</div>
155
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background:${COLOR_BG};">
156
+ <tr><td align="center" style="padding:40px 16px;">
157
+ <table width="600" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;border-radius:12px;border:1px solid ${COLOR_BORDER};overflow:hidden;max-width:600px;width:100%;">
158
+ <tr><td style="background:${BRAND_GRADIENT};padding:32px 40px;">
159
+ <h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">${escapeHtml(brandName)}</h1>
160
+ </td></tr>
161
+ <tr><td style="padding:40px;">
162
+ ${body}
163
+ </td></tr>
164
+ <tr><td style="padding:24px 40px;border-top:1px solid ${COLOR_BORDER};background:${COLOR_BG};">
165
+ <p style="margin:0;font-size:12px;color:${COLOR_MUTED};text-align:center;">
166
+ &copy; ${(/* @__PURE__ */ new Date()).getFullYear()} ${escapeHtml(brandName)} &middot;
167
+ <a href="https://nebutra.ai/privacy" style="color:${COLOR_MUTED};">Privacy</a> &middot;
168
+ <a href="https://nebutra.ai/unsubscribe" style="color:${COLOR_MUTED};">Unsubscribe</a>
169
+ </p>
170
+ </td></tr>
171
+ </table>
172
+ </td></tr>
173
+ </table>
174
+ </body>
175
+ </html>`;
176
+ }
177
+ var palette = {
178
+ gradient: BRAND_GRADIENT,
179
+ background: COLOR_BG,
180
+ border: COLOR_BORDER,
181
+ heading: COLOR_HEADING,
182
+ body: COLOR_BODY,
183
+ muted: COLOR_MUTED
184
+ };
185
+ function escapeHtml(value) {
186
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
187
+ }
188
+
189
+ // src/templates/invitation.tsx
190
+ function subject(props) {
191
+ return `${props.inviterName} invited you to join ${props.organizationName}`;
192
+ }
193
+ function preview(props) {
194
+ return `You've been invited to ${props.organizationName} as ${props.role}`;
195
+ }
196
+ function render(props) {
197
+ const inviterName = escapeHtml(props.inviterName);
198
+ const organizationName = escapeHtml(props.organizationName);
199
+ const role = escapeHtml(props.role);
200
+ const acceptUrl = escapeHtml(props.acceptUrl);
201
+ const expiresAt = escapeHtml(props.expiresAt);
202
+ const brandName = escapeHtml(props.brandName);
203
+ const body = `
204
+ <h2 style="margin:0 0 16px;font-size:22px;color:${palette.heading};">You're invited to ${organizationName}</h2>
205
+ <p style="margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;">
206
+ <strong>${inviterName}</strong> has invited you to collaborate on
207
+ <strong>${organizationName}</strong>.
208
+ </p>
209
+ <p style="margin:0 0 24px;font-size:14px;color:${palette.body};">
210
+ Role:
211
+ <span style="display:inline-block;background:#e0f2fe;color:#0033FE;padding:4px 10px;border-radius:999px;font-size:13px;font-weight:600;margin-left:6px;">
212
+ ${role}
213
+ </span>
214
+ </p>
215
+ <a href="${acceptUrl}" style="display:inline-block;background:${palette.gradient};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
216
+ Accept invitation &rarr;
217
+ </a>
218
+ <p style="margin:0 0 12px;font-size:13px;color:${palette.muted};">
219
+ This invitation expires on <strong>${expiresAt}</strong>.
220
+ </p>
221
+ <p style="margin:0;font-size:13px;color:${palette.muted};">
222
+ Sent by ${brandName} on behalf of ${organizationName}. If you weren't expecting this, you can
223
+ safely ignore the message.
224
+ </p>
225
+ `;
226
+ return baseLayout({
227
+ brandName: props.brandName,
228
+ preview: preview(props),
229
+ body
230
+ });
231
+ }
232
+ var invitationEmail = { subject, preview, render };
233
+
234
+ // src/templates/password-reset.tsx
235
+ function subject2(props) {
236
+ return `Reset your ${props.brandName} password`;
237
+ }
238
+ function preview2(props) {
239
+ return `Reset link expires in ${props.expiresInMinutes} minutes`;
240
+ }
241
+ function render2(props) {
242
+ const userName = escapeHtml(props.userName);
243
+ const resetUrl = escapeHtml(props.resetUrl);
244
+ const brandName = escapeHtml(props.brandName);
245
+ const expires = String(props.expiresInMinutes);
246
+ const body = `
247
+ <h2 style="margin:0 0 16px;font-size:22px;color:${palette.heading};">Reset your password</h2>
248
+ <p style="margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;">
249
+ Hi ${userName}, we received a request to reset the password on your ${brandName} account.
250
+ Click the button below to choose a new one.
251
+ </p>
252
+ <a href="${resetUrl}" style="display:inline-block;background:${palette.gradient};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
253
+ Reset password &rarr;
254
+ </a>
255
+ <p style="margin:0 0 16px;font-size:14px;color:${palette.body};line-height:1.6;">
256
+ This link expires in <strong>${expires} minutes</strong>. If it's expired, request a new one
257
+ from the sign-in page.
258
+ </p>
259
+ <p style="margin:0;font-size:13px;color:${palette.muted};line-height:1.5;">
260
+ If this wasn't you, you can safely ignore this email &mdash; your password won't change unless
261
+ you click the link above. For added safety, review recent sign-ins on your account.
262
+ </p>
263
+ `;
264
+ return baseLayout({
265
+ brandName: props.brandName,
266
+ preview: preview2(props),
267
+ body
268
+ });
269
+ }
270
+ var passwordResetEmail = { subject: subject2, preview: preview2, render: render2 };
271
+
272
+ // src/templates/receipt.tsx
273
+ function subject3(props) {
274
+ return `Receipt for ${props.invoiceNumber}`;
275
+ }
276
+ function preview3(props) {
277
+ return `Payment of ${props.amount} ${props.currency} received`;
278
+ }
279
+ function render3(props) {
280
+ const customerName = escapeHtml(props.customerName);
281
+ const invoiceNumber = escapeHtml(props.invoiceNumber);
282
+ const amount = escapeHtml(props.amount);
283
+ const currency = escapeHtml(props.currency);
284
+ const periodStart = escapeHtml(props.periodStart);
285
+ const periodEnd = escapeHtml(props.periodEnd);
286
+ const downloadUrl = escapeHtml(props.downloadUrl);
287
+ const brandName = escapeHtml(props.brandName);
288
+ const body = `
289
+ <h2 style="margin:0 0 16px;font-size:22px;color:${palette.heading};">Payment received &check;</h2>
290
+ <p style="margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;">
291
+ Hi ${customerName}, thank you for your payment. Here's a summary of your latest invoice from
292
+ ${brandName}.
293
+ </p>
294
+ <table width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 24px;border-collapse:collapse;border:1px solid ${palette.border};border-radius:8px;overflow:hidden;">
295
+ <tr>
296
+ <td style="padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:13px;color:${palette.muted};">Invoice number</td>
297
+ <td style="padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:14px;color:${palette.heading};text-align:right;font-weight:600;">${invoiceNumber}</td>
298
+ </tr>
299
+ <tr>
300
+ <td style="padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:13px;color:${palette.muted};">Billing period</td>
301
+ <td style="padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:14px;color:${palette.heading};text-align:right;">${periodStart} &mdash; ${periodEnd}</td>
302
+ </tr>
303
+ <tr>
304
+ <td style="padding:12px 16px;font-size:13px;color:${palette.muted};">Amount paid</td>
305
+ <td style="padding:12px 16px;font-size:14px;color:#16a34a;text-align:right;font-weight:700;">${amount} ${currency}</td>
306
+ </tr>
307
+ </table>
308
+ <a href="${downloadUrl}" style="display:inline-block;background:#0f172a;color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
309
+ Download PDF receipt &rarr;
310
+ </a>
311
+ <p style="margin:0;font-size:13px;color:${palette.muted};">
312
+ Keep this receipt for your records. Questions about this charge? Reply to this email and the
313
+ ${brandName} billing team will help.
314
+ </p>
315
+ `;
316
+ return baseLayout({
317
+ brandName: props.brandName,
318
+ preview: preview3(props),
319
+ body
320
+ });
321
+ }
322
+ var receiptEmail = { subject: subject3, preview: preview3, render: render3 };
323
+
324
+ // src/templates/welcome.tsx
325
+ function subject4(props) {
326
+ return `Welcome to ${props.brandName}`;
327
+ }
328
+ function preview4(_props) {
329
+ return "Get started with your account";
330
+ }
331
+ function render4(props) {
332
+ const userName = escapeHtml(props.userName);
333
+ const loginUrl = escapeHtml(props.loginUrl);
334
+ const brandName = escapeHtml(props.brandName);
335
+ const body = `
336
+ <h2 style="margin:0 0 16px;font-size:22px;color:${palette.heading};">Welcome, ${userName}!</h2>
337
+ <p style="margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;">
338
+ We're glad to have you with ${brandName}. Your account is ready &mdash; sign in to
339
+ explore the dashboard, set up your workspace, and invite the rest of your team.
340
+ </p>
341
+ <a href="${loginUrl}" style="display:inline-block;background:${palette.gradient};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
342
+ Get Started &rarr;
343
+ </a>
344
+ <p style="margin:0;font-size:13px;color:${palette.muted};">
345
+ Need help? Reply to this email and a real human from the ${brandName} team will get back to you.
346
+ </p>
347
+ `;
348
+ return baseLayout({
349
+ brandName: props.brandName,
350
+ preview: preview4(props),
351
+ body
352
+ });
353
+ }
354
+ var welcomeEmail = { subject: subject4, preview: preview4, render: render4 };
355
+
356
+ // src/templates/index.ts
357
+ var REACT_EMAIL_TEMPLATES = {
358
+ welcome: {
359
+ id: "welcome-react",
360
+ label: "Welcome (React Email)",
361
+ description: "Account onboarding welcome message",
362
+ fileName: "welcome-react-email.html",
363
+ ...welcomeEmail
364
+ },
365
+ passwordReset: {
366
+ id: "password-reset",
367
+ label: "Password Reset",
368
+ description: "Secure password reset link with expiry",
369
+ fileName: "password-reset-email.html",
370
+ ...passwordResetEmail
371
+ },
372
+ invitation: {
373
+ id: "invitation",
374
+ label: "Invitation",
375
+ description: "Workspace invitation with role and expiry",
376
+ fileName: "invitation-email.html",
377
+ ...invitationEmail
378
+ },
379
+ receipt: {
380
+ id: "receipt",
381
+ label: "Receipt",
382
+ description: "Payment receipt with billing period and PDF link",
383
+ fileName: "receipt-email.html",
384
+ ...receiptEmail
385
+ }
386
+ };
387
+
388
+ // src/index.ts
389
+ var FROM = process.env.EMAIL_FROM ?? "Nebutra <noreply@nebutra.ai>";
390
+ var EMAIL_TEMPLATE_CATALOG = [
391
+ {
392
+ id: "welcome",
393
+ label: "Welcome",
394
+ description: "Workspace provisioning welcome email",
395
+ sendHelper: "sendWelcomeEmail",
396
+ fileName: "welcome-email.html"
397
+ },
398
+ {
399
+ id: "order-confirmation",
400
+ label: "Order Confirmation",
401
+ description: "Commerce order receipt with line items",
402
+ sendHelper: "sendOrderConfirmationEmail",
403
+ fileName: "order-confirmation-email.html"
404
+ },
405
+ {
406
+ id: "magic-link",
407
+ label: "Magic Link",
408
+ description: "Passwordless sign-in link",
409
+ sendHelper: "sendMagicLinkEmail",
410
+ fileName: "magic-link-email.html"
411
+ },
412
+ {
413
+ id: "contact-form-received",
414
+ label: "Contact Form Received",
415
+ description: "Contact form acknowledgement",
416
+ sendHelper: "sendContactFormReceivedEmail",
417
+ fileName: "contact-form-received-email.html"
418
+ },
419
+ {
420
+ id: "license-created",
421
+ label: "License Created",
422
+ description: "License key delivery email",
423
+ sendHelper: "sendLicenseCreatedEmail",
424
+ fileName: "license-created-email.html"
425
+ },
426
+ {
427
+ id: "welcome-react",
428
+ label: "Welcome (React Email)",
429
+ description: "Account onboarding welcome message",
430
+ sendHelper: "sendWelcomeReactEmail",
431
+ fileName: "welcome-react-email.html"
432
+ },
433
+ {
434
+ id: "password-reset",
435
+ label: "Password Reset",
436
+ description: "Secure password reset link with expiry",
437
+ sendHelper: "sendPasswordResetEmail",
438
+ fileName: "password-reset-email.html"
439
+ },
440
+ {
441
+ id: "invitation",
442
+ label: "Invitation",
443
+ description: "Workspace invitation with role and expiry",
444
+ sendHelper: "sendInvitationEmail",
445
+ fileName: "invitation-email.html"
446
+ },
447
+ {
448
+ id: "receipt",
449
+ label: "Receipt",
450
+ description: "Payment receipt with billing period and PDF link",
451
+ sendHelper: "sendReceiptEmail",
452
+ fileName: "receipt-email.html"
453
+ }
454
+ ];
455
+ async function send(opts) {
456
+ const provider = getEmailProvider();
457
+ return provider.send({
458
+ ...opts,
459
+ from: FROM
460
+ });
461
+ }
462
+ function baseLayout2(content, previewText = "") {
463
+ return `<!DOCTYPE html>
464
+ <html lang="en">
465
+ <head>
466
+ <meta charset="UTF-8" />
467
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
468
+ <meta name="x-apple-disable-message-reformatting" />
469
+ <title>Nebutra</title>
470
+ <!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->
471
+ </head>
472
+ <body style="margin:0;padding:0;background:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
473
+ ${previewText ? `<div style="display:none;max-height:0;overflow:hidden;">${previewText}&nbsp;\u200C&zwnj;&nbsp;</div>` : ""}
474
+ <table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background:#f8fafc;">
475
+ <tr><td align="center" style="padding:40px 16px;">
476
+ <table width="600" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;border-radius:12px;border:1px solid #e2e8f0;overflow:hidden;max-width:600px;width:100%;">
477
+ <!-- Header -->
478
+ <tr><td style="background:linear-gradient(135deg,#0033FE,#0BF1C3);padding:32px 40px;">
479
+ <h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">Nebutra</h1>
480
+ </td></tr>
481
+ <!-- Body -->
482
+ <tr><td style="padding:40px;">
483
+ ${content}
484
+ </td></tr>
485
+ <!-- Footer -->
486
+ <tr><td style="padding:24px 40px;border-top:1px solid #e2e8f0;background:#f8fafc;">
487
+ <p style="margin:0;font-size:12px;color:#94a3b8;text-align:center;">
488
+ \xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} Nebutra Intelligence Inc. \xB7
489
+ <a href="https://nebutra.ai/privacy" style="color:#94a3b8;">Privacy</a> \xB7
490
+ <a href="https://nebutra.ai/unsubscribe" style="color:#94a3b8;">Unsubscribe</a>
491
+ </p>
492
+ </td></tr>
493
+ </table>
494
+ </td></tr>
495
+ </table>
496
+ </body>
497
+ </html>`;
498
+ }
499
+ async function sendWelcomeEmail(opts) {
500
+ const dashboardUrl = opts.dashboardUrl ?? "https://app.nebutra.ai";
501
+ const html = baseLayout2(
502
+ `
503
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to Nebutra, ${opts.firstName}!</h2>
504
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
505
+ Your workspace <strong>${opts.orgName}</strong> is ready. You can now invite team members,
506
+ create API keys, and start building with the Nebutra platform.
507
+ </p>
508
+ <a href="${dashboardUrl}" style="display:inline-block;background:linear-gradient(135deg,#0033FE,#0BF1C3);color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
509
+ Open Dashboard \u2192
510
+ </a>
511
+ <p style="margin:0;font-size:13px;color:#94a3b8;">
512
+ If you have questions, reply to this email or visit our <a href="https://docs.nebutra.ai" style="color:#0033FE;">documentation</a>.
513
+ </p>
514
+ `,
515
+ `Welcome to Nebutra \u2014 ${opts.orgName} is ready`
516
+ );
517
+ return send({
518
+ to: opts.to,
519
+ subject: `Welcome to Nebutra \u2014 ${opts.orgName} is ready`,
520
+ html,
521
+ tags: [{ name: "type", value: "welcome" }]
522
+ });
523
+ }
524
+ async function sendOrderConfirmationEmail(opts) {
525
+ const itemsHtml = opts.items.map(
526
+ (item) => `<tr>
527
+ <td style="padding:12px 0;border-bottom:1px solid #e2e8f0;color:#0f172a;font-size:15px;">${item.productId}</td>
528
+ <td style="padding:12px 0;border-bottom:1px solid #e2e8f0;color:#475569;font-size:15px;text-align:right;">x${item.quantity}</td>
529
+ </tr>`
530
+ ).join("");
531
+ const html = baseLayout2(
532
+ `
533
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Order Confirmed!</h2>
534
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
535
+ Thank you for your order <strong>#${opts.orderId}</strong>. We've received your payment of
536
+ <strong>$${(opts.totalAmount / 100).toFixed(2)}</strong> and your order is now being processed.
537
+ </p>
538
+ <table width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 24px;border-collapse:collapse;">
539
+ ${itemsHtml}
540
+ </table>
541
+ <a href="https://app.nebutra.ai/orders/${opts.orderId}" style="display:inline-block;background:linear-gradient(135deg,#0033FE,#0BF1C3);color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
542
+ View Order Details \u2192
543
+ </a>
544
+ `,
545
+ `Order #${opts.orderId} Confirmed`
546
+ );
547
+ return send({
548
+ to: opts.to,
549
+ subject: `Order #${opts.orderId} Confirmed`,
550
+ html,
551
+ tags: [{ name: "type", value: "order_confirmation" }]
552
+ });
553
+ }
554
+ async function sendMagicLinkEmail(opts) {
555
+ const html = baseLayout2(
556
+ `
557
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Sign in to Nebutra</h2>
558
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
559
+ Click the button below to sign in securely. No password required.
560
+ </p>
561
+ <a href="${opts.magicLinkUrl}" style="display:inline-block;background:linear-gradient(135deg,#0033FE,#0BF1C3);color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:16px;font-weight:600;margin:0 0 24px;text-align:center;width:100%;max-width:280px;">
562
+ \u2728 Sign In Automatically
563
+ </a>
564
+ <p style="margin:0;font-size:12px;color:#94a3b8;line-height:1.4;">
565
+ If you did not request this email, you can safely ignore it.
566
+ This link expires in 15 minutes.
567
+ </p>
568
+ `,
569
+ "Click here to sign in to your Nebutra account"
570
+ );
571
+ return send({
572
+ to: opts.to,
573
+ subject: "Sign in to Nebutra \u2728",
574
+ html,
575
+ tags: [{ name: "type", value: "magic_link" }]
576
+ });
577
+ }
578
+ async function sendContactFormReceivedEmail(opts) {
579
+ const html = baseLayout2(
580
+ `
581
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Message Received</h2>
582
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
583
+ Hi ${opts.name},
584
+ </p>
585
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
586
+ Thank you for contacting us regarding "<strong>${opts.subject}</strong>".
587
+ We've received your request and our team will get back to you within 1-2 business days.
588
+ </p>
589
+ <p style="margin:0;font-size:15px;color:#475569;line-height:1.6;">
590
+ Best regards,<br/>The Nebutra Team
591
+ </p>
592
+ `,
593
+ "We have received your message"
594
+ );
595
+ return send({
596
+ to: opts.to,
597
+ subject: "We've received your message - Nebutra",
598
+ html,
599
+ tags: [{ name: "type", value: "contact_receipt" }]
600
+ });
601
+ }
602
+ async function sendLicenseCreatedEmail(opts) {
603
+ const html = baseLayout2(
604
+ `
605
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to Nebutra-Sailor! \u{1F680}</h2>
606
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
607
+ Hi ${opts.firstName}, your <strong>${opts.tier}</strong> license has been successfully generated.
608
+ </p>
609
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
610
+ Your official License Key is:
611
+ </p>
612
+ <div style="background:#0f172a;border-radius:8px;padding:16px 20px;margin:0 0 24px;overflow-x:auto;">
613
+ <code style="color:#0BF1C3;font-family:'Courier New',monospace;font-size:13px;word-break:break-all;">${opts.licenseKey}</code>
614
+ </div>
615
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
616
+ To unlock the premium capabilities of the Nebutra CLI locally, run the following command in your terminal:
617
+ </p>
618
+ <div style="background:#f1f5f9;border:1px solid #e2e8f0;border-radius:6px;padding:12px;margin:0 0 24px;">
619
+ <code style="color:#334155;font-family:'Courier New',monospace;font-size:13px;">nebutra license activate ${opts.licenseKey}</code>
620
+ </div>
621
+ <p style="margin:0;font-size:13px;color:#94a3b8;">
622
+ If you have questions, reply to this email or visit our <a href="https://docs.nebutra.ai" style="color:#0033FE;">documentation</a>.
623
+ </p>
624
+ `,
625
+ "Your Nebutra License Key is Ready"
626
+ );
627
+ return send({
628
+ to: opts.to,
629
+ subject: `Your Nebutra License Key is Ready (${opts.tier})`,
630
+ html,
631
+ tags: [{ name: "type", value: "license_created" }]
632
+ });
633
+ }
634
+ async function sendWelcomeReactEmail(opts) {
635
+ return send({
636
+ to: opts.to,
637
+ subject: welcomeEmail.subject(opts),
638
+ html: welcomeEmail.render(opts),
639
+ tags: [{ name: "type", value: "welcome_react" }]
640
+ });
641
+ }
642
+ async function sendPasswordResetEmail(opts) {
643
+ return send({
644
+ to: opts.to,
645
+ subject: passwordResetEmail.subject(opts),
646
+ html: passwordResetEmail.render(opts),
647
+ tags: [{ name: "type", value: "password_reset" }]
648
+ });
649
+ }
650
+ async function sendInvitationEmail(opts) {
651
+ return send({
652
+ to: opts.to,
653
+ subject: invitationEmail.subject(opts),
654
+ html: invitationEmail.render(opts),
655
+ tags: [{ name: "type", value: "invitation" }]
656
+ });
657
+ }
658
+ async function sendReceiptEmail(opts) {
659
+ return send({
660
+ to: opts.to,
661
+ subject: receiptEmail.subject(opts),
662
+ html: receiptEmail.render(opts),
663
+ tags: [{ name: "type", value: "receipt" }]
664
+ });
665
+ }
666
+ export {
667
+ ConsoleEmailProvider,
668
+ EMAIL_TEMPLATE_CATALOG,
669
+ NodemailerEmailProvider,
670
+ REACT_EMAIL_TEMPLATES,
671
+ ResendEmailProvider,
672
+ getEmailProvider,
673
+ invitationEmail,
674
+ passwordResetEmail,
675
+ receiptEmail,
676
+ render as renderInvitationEmail,
677
+ render2 as renderPasswordResetEmail,
678
+ render3 as renderReceiptEmail,
679
+ render4 as renderWelcomeEmail,
680
+ resetEmailProvider,
681
+ sendContactFormReceivedEmail,
682
+ sendInvitationEmail,
683
+ sendLicenseCreatedEmail,
684
+ sendMagicLinkEmail,
685
+ sendOrderConfirmationEmail,
686
+ sendPasswordResetEmail,
687
+ sendReceiptEmail,
688
+ sendWelcomeEmail,
689
+ sendWelcomeReactEmail,
690
+ welcomeEmail
691
+ };
692
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/console.ts","../src/providers/nodemailer.ts","../src/providers/resend.ts","../src/provider.ts","../src/templates/_layout.ts","../src/templates/invitation.tsx","../src/templates/password-reset.tsx","../src/templates/receipt.tsx","../src/templates/welcome.tsx","../src/templates/index.ts","../src/index.ts"],"sourcesContent":["/**\n * Console email provider — prints email details to stdout.\n * Perfect for local development and testing without API keys.\n */\n\nimport type { EmailProvider, SendOptions, SendResult } from \"../provider\";\n\nlet counter = 0;\n\nexport class ConsoleEmailProvider implements EmailProvider {\n readonly name = \"console\" as const;\n\n async send(opts: SendOptions): Promise<SendResult> {\n counter += 1;\n const id = `console_${Date.now()}_${counter}`;\n const recipients = Array.isArray(opts.to) ? opts.to.join(\", \") : opts.to;\n const separator = \"─\".repeat(60);\n\n const lines = [\n `\\n${separator}`,\n `📧 Email (console provider)`,\n `${separator}`,\n `From: ${opts.from}`,\n `To: ${recipients}`,\n `Subject: ${opts.subject}`,\n ];\n\n if (opts.replyTo) {\n lines.push(`Reply-To: ${opts.replyTo}`);\n }\n\n if (opts.tags?.length) {\n const tagStr = opts.tags.map((t) => `${t.name}=${t.value}`).join(\", \");\n lines.push(`Tags: ${tagStr}`);\n }\n\n lines.push(`ID: ${id}`);\n lines.push(`${separator}\\n`);\n\n process.stdout.write(lines.join(\"\\n\"));\n\n return { id };\n }\n}\n","/**\n * Nodemailer email provider — SMTP-based, self-hosted fallback.\n * Requires SMTP_HOST (+ optional SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE).\n */\n\nimport type { EmailProvider, SendOptions, SendResult } from \"../provider\";\n\nexport interface NodemailerConfig {\n host: string;\n port: number;\n secure: boolean;\n auth?:\n | {\n user: string;\n pass: string;\n }\n | undefined;\n}\n\nexport class NodemailerEmailProvider implements EmailProvider {\n readonly name = \"nodemailer\" as const;\n private config: NodemailerConfig;\n private transporter: unknown;\n\n constructor(config: NodemailerConfig) {\n if (!config.host) {\n throw new Error(\"SMTP_HOST is required for Nodemailer provider\");\n }\n this.config = config;\n }\n\n private async getTransporter() {\n if (this.transporter) return this.transporter;\n\n // Lazy import to avoid requiring nodemailer when using other providers\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const nodemailer = await (import(\"nodemailer\" as string) as Promise<{\n createTransport(opts: Record<string, unknown>): {\n sendMail(opts: Record<string, unknown>): Promise<{ messageId: string }>;\n };\n }>);\n this.transporter = nodemailer.createTransport({\n host: this.config.host,\n port: this.config.port,\n secure: this.config.secure,\n ...(this.config.auth ? { auth: this.config.auth } : {}),\n });\n return this.transporter;\n }\n\n async send(opts: SendOptions): Promise<SendResult> {\n const transporter = (await this.getTransporter()) as {\n sendMail(opts: Record<string, unknown>): Promise<{ messageId: string }>;\n };\n\n const info = await transporter.sendMail({\n from: opts.from,\n to: Array.isArray(opts.to) ? opts.to.join(\", \") : opts.to,\n subject: opts.subject,\n html: opts.html,\n ...(opts.replyTo ? { replyTo: opts.replyTo } : {}),\n ...(opts.tags?.length\n ? {\n headers: {\n \"X-Email-Tags\": opts.tags.map((t) => `${t.name}=${t.value}`).join(\", \"),\n },\n }\n : {}),\n });\n\n return { id: info.messageId };\n }\n}\n","/**\n * Resend email provider — production-grade transactional email.\n * Requires RESEND_API_KEY environment variable.\n */\n\nimport { Resend } from \"resend\";\nimport type { EmailProvider, SendOptions, SendResult } from \"../provider\";\n\nexport class ResendEmailProvider implements EmailProvider {\n readonly name = \"resend\" as const;\n private client: Resend;\n\n constructor(apiKey: string) {\n if (!apiKey) {\n throw new Error(\"RESEND_API_KEY is required for Resend provider\");\n }\n this.client = new Resend(apiKey);\n }\n\n async send(opts: SendOptions): Promise<SendResult> {\n const { data, error } = await this.client.emails.send({\n from: opts.from,\n to: Array.isArray(opts.to) ? opts.to : [opts.to],\n subject: opts.subject,\n html: opts.html,\n ...(opts.replyTo ? { replyTo: opts.replyTo } : {}),\n ...(opts.tags ? { tags: opts.tags } : {}),\n });\n\n if (error) {\n throw new Error(`Resend send failed: ${error.message}`);\n }\n\n return { id: data?.id ?? \"unknown\" };\n }\n}\n","/**\n * @nebutra/email — Provider abstraction\n *\n * Supports multiple email providers via auto-detection:\n * 1. EMAIL_PROVIDER env var (explicit)\n * 2. RESEND_API_KEY → Resend\n * 3. SMTP_HOST → Nodemailer\n * 4. Fallback → Console (dev/test)\n */\n\n// ── Interface ────────────────────────────────────────────────────────────────\n\nexport interface SendOptions {\n to: string | string[];\n subject: string;\n html: string;\n from: string;\n replyTo?: string;\n tags?: { name: string; value: string }[];\n}\n\nexport interface SendResult {\n id: string;\n}\n\nexport interface EmailProvider {\n readonly name: string;\n send(opts: SendOptions): Promise<SendResult>;\n}\n\n// ── Provider types ───────────────────────────────────────────────────────────\n\nexport type EmailProviderType = \"resend\" | \"nodemailer\" | \"console\";\n\n// ── Factory ──────────────────────────────────────────────────────────────────\n\nimport { ConsoleEmailProvider } from \"./providers/console\";\nimport { NodemailerEmailProvider } from \"./providers/nodemailer\";\nimport { ResendEmailProvider } from \"./providers/resend\";\n\nlet cachedProvider: EmailProvider | undefined;\n\nfunction detectProviderType(): EmailProviderType {\n const explicit = process.env.EMAIL_PROVIDER;\n if (explicit === \"resend\" || explicit === \"nodemailer\" || explicit === \"console\") {\n return explicit;\n }\n\n if (process.env.RESEND_API_KEY) return \"resend\";\n if (process.env.SMTP_HOST) return \"nodemailer\";\n return \"console\";\n}\n\nfunction createProvider(type: EmailProviderType): EmailProvider {\n switch (type) {\n case \"resend\":\n return new ResendEmailProvider(process.env.RESEND_API_KEY!);\n case \"nodemailer\":\n return new NodemailerEmailProvider({\n host: process.env.SMTP_HOST!,\n port: Number(process.env.SMTP_PORT ?? \"587\"),\n secure: process.env.SMTP_SECURE === \"true\",\n auth: process.env.SMTP_USER\n ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS ?? \"\" }\n : undefined,\n });\n case \"console\":\n return new ConsoleEmailProvider();\n }\n}\n\nexport function getEmailProvider(): EmailProvider {\n if (cachedProvider) return cachedProvider;\n const type = detectProviderType();\n cachedProvider = createProvider(type);\n return cachedProvider;\n}\n\nexport function resetEmailProvider(): void {\n cachedProvider = undefined;\n}\n","/**\n * Shared HTML layout for the React Email-style template catalog.\n *\n * NOTE: react-email is NOT installed in this workspace. This module ships a\n * plain template literal layout instead so the templates remain framework-free\n * and can be rendered without a React runtime. The contract still mirrors a\n * future React Email migration: each template module exports\n * `{ subject, preview, render }` and the renderer returns a complete HTML\n * document.\n */\n\nexport interface RenderedEmail {\n html: string;\n subject: string;\n preview: string;\n}\n\nconst BRAND_GRADIENT = \"linear-gradient(135deg,#0033FE,#0BF1C3)\";\nconst COLOR_BG = \"#f8fafc\";\nconst COLOR_BORDER = \"#e2e8f0\";\nconst COLOR_HEADING = \"#0f172a\";\nconst COLOR_BODY = \"#475569\";\nconst COLOR_MUTED = \"#94a3b8\";\n\nexport interface BaseLayoutOptions {\n brandName: string;\n preview: string;\n body: string;\n}\n\n/**\n * Wrap a body fragment with the standard Nebutra-style transactional shell.\n */\nexport function baseLayout({ brandName, preview, body }: BaseLayoutOptions): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"x-apple-disable-message-reformatting\" />\n <title>${escapeHtml(brandName)}</title>\n</head>\n<body style=\"margin:0;padding:0;background:${COLOR_BG};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\">\n <div style=\"display:none;max-height:0;overflow:hidden;\">${escapeHtml(preview)}&nbsp;&zwnj;&nbsp;</div>\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"background:${COLOR_BG};\">\n <tr><td align=\"center\" style=\"padding:40px 16px;\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"background:#ffffff;border-radius:12px;border:1px solid ${COLOR_BORDER};overflow:hidden;max-width:600px;width:100%;\">\n <tr><td style=\"background:${BRAND_GRADIENT};padding:32px 40px;\">\n <h1 style=\"margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;\">${escapeHtml(brandName)}</h1>\n </td></tr>\n <tr><td style=\"padding:40px;\">\n ${body}\n </td></tr>\n <tr><td style=\"padding:24px 40px;border-top:1px solid ${COLOR_BORDER};background:${COLOR_BG};\">\n <p style=\"margin:0;font-size:12px;color:${COLOR_MUTED};text-align:center;\">\n &copy; ${new Date().getFullYear()} ${escapeHtml(brandName)} &middot;\n <a href=\"https://nebutra.ai/privacy\" style=\"color:${COLOR_MUTED};\">Privacy</a> &middot;\n <a href=\"https://nebutra.ai/unsubscribe\" style=\"color:${COLOR_MUTED};\">Unsubscribe</a>\n </p>\n </td></tr>\n </table>\n </td></tr>\n </table>\n</body>\n</html>`;\n}\n\nexport const palette = {\n gradient: BRAND_GRADIENT,\n background: COLOR_BG,\n border: COLOR_BORDER,\n heading: COLOR_HEADING,\n body: COLOR_BODY,\n muted: COLOR_MUTED,\n} as const;\n\n/**\n * Conservative HTML escape for user-supplied template props.\n *\n * Templates accept structured props from trusted backend code, but escaping is\n * still required because some props (e.g. organization names, invoice numbers)\n * may originate from end-user input.\n */\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&#39;\");\n}\n","/**\n * InvitationEmail — workspace member invitation with role and expiry.\n *\n * react-email is not installed; this module exposes the same\n * `{ subject, preview, render }` contract using a plain template literal so the\n * catalog can be migrated to React Email later without changing call sites.\n */\n\nimport { baseLayout, escapeHtml, palette } from \"./_layout\";\n\nexport interface InvitationEmailProps {\n inviterName: string;\n organizationName: string;\n role: string;\n acceptUrl: string;\n expiresAt: string;\n brandName: string;\n}\n\nexport function subject(props: InvitationEmailProps): string {\n return `${props.inviterName} invited you to join ${props.organizationName}`;\n}\n\nexport function preview(props: InvitationEmailProps): string {\n return `You've been invited to ${props.organizationName} as ${props.role}`;\n}\n\nexport function render(props: InvitationEmailProps): string {\n const inviterName = escapeHtml(props.inviterName);\n const organizationName = escapeHtml(props.organizationName);\n const role = escapeHtml(props.role);\n const acceptUrl = escapeHtml(props.acceptUrl);\n const expiresAt = escapeHtml(props.expiresAt);\n const brandName = escapeHtml(props.brandName);\n\n const body = `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:${palette.heading};\">You're invited to ${organizationName}</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;\">\n <strong>${inviterName}</strong> has invited you to collaborate on\n <strong>${organizationName}</strong>.\n </p>\n <p style=\"margin:0 0 24px;font-size:14px;color:${palette.body};\">\n Role:\n <span style=\"display:inline-block;background:#e0f2fe;color:#0033FE;padding:4px 10px;border-radius:999px;font-size:13px;font-weight:600;margin-left:6px;\">\n ${role}\n </span>\n </p>\n <a href=\"${acceptUrl}\" style=\"display:inline-block;background:${palette.gradient};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;\">\n Accept invitation &rarr;\n </a>\n <p style=\"margin:0 0 12px;font-size:13px;color:${palette.muted};\">\n This invitation expires on <strong>${expiresAt}</strong>.\n </p>\n <p style=\"margin:0;font-size:13px;color:${palette.muted};\">\n Sent by ${brandName} on behalf of ${organizationName}. If you weren't expecting this, you can\n safely ignore the message.\n </p>\n `;\n\n return baseLayout({\n brandName: props.brandName,\n preview: preview(props),\n body,\n });\n}\n\nexport const invitationEmail = { subject, preview, render } as const;\n","/**\n * PasswordResetEmail — secure password reset link with expiry.\n *\n * react-email is not installed; this module exposes the same\n * `{ subject, preview, render }` contract using a plain template literal so the\n * catalog can be migrated to React Email later without changing call sites.\n */\n\nimport { baseLayout, escapeHtml, palette } from \"./_layout\";\n\nexport interface PasswordResetEmailProps {\n userName: string;\n resetUrl: string;\n expiresInMinutes: number;\n brandName: string;\n}\n\nexport function subject(props: PasswordResetEmailProps): string {\n return `Reset your ${props.brandName} password`;\n}\n\nexport function preview(props: PasswordResetEmailProps): string {\n return `Reset link expires in ${props.expiresInMinutes} minutes`;\n}\n\nexport function render(props: PasswordResetEmailProps): string {\n const userName = escapeHtml(props.userName);\n const resetUrl = escapeHtml(props.resetUrl);\n const brandName = escapeHtml(props.brandName);\n const expires = String(props.expiresInMinutes);\n\n const body = `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:${palette.heading};\">Reset your password</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;\">\n Hi ${userName}, we received a request to reset the password on your ${brandName} account.\n Click the button below to choose a new one.\n </p>\n <a href=\"${resetUrl}\" style=\"display:inline-block;background:${palette.gradient};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;\">\n Reset password &rarr;\n </a>\n <p style=\"margin:0 0 16px;font-size:14px;color:${palette.body};line-height:1.6;\">\n This link expires in <strong>${expires} minutes</strong>. If it's expired, request a new one\n from the sign-in page.\n </p>\n <p style=\"margin:0;font-size:13px;color:${palette.muted};line-height:1.5;\">\n If this wasn't you, you can safely ignore this email &mdash; your password won't change unless\n you click the link above. For added safety, review recent sign-ins on your account.\n </p>\n `;\n\n return baseLayout({\n brandName: props.brandName,\n preview: preview(props),\n body,\n });\n}\n\nexport const passwordResetEmail = { subject, preview, render } as const;\n","/**\n * ReceiptEmail — payment confirmation with billing period and invoice download.\n *\n * react-email is not installed; this module exposes the same\n * `{ subject, preview, render }` contract using a plain template literal so the\n * catalog can be migrated to React Email later without changing call sites.\n */\n\nimport { baseLayout, escapeHtml, palette } from \"./_layout\";\n\nexport interface ReceiptEmailProps {\n customerName: string;\n invoiceNumber: string;\n amount: string;\n currency: string;\n periodStart: string;\n periodEnd: string;\n downloadUrl: string;\n brandName: string;\n}\n\nexport function subject(props: ReceiptEmailProps): string {\n return `Receipt for ${props.invoiceNumber}`;\n}\n\nexport function preview(props: ReceiptEmailProps): string {\n return `Payment of ${props.amount} ${props.currency} received`;\n}\n\nexport function render(props: ReceiptEmailProps): string {\n const customerName = escapeHtml(props.customerName);\n const invoiceNumber = escapeHtml(props.invoiceNumber);\n const amount = escapeHtml(props.amount);\n const currency = escapeHtml(props.currency);\n const periodStart = escapeHtml(props.periodStart);\n const periodEnd = escapeHtml(props.periodEnd);\n const downloadUrl = escapeHtml(props.downloadUrl);\n const brandName = escapeHtml(props.brandName);\n\n const body = `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:${palette.heading};\">Payment received &check;</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;\">\n Hi ${customerName}, thank you for your payment. Here's a summary of your latest invoice from\n ${brandName}.\n </p>\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 24px;border-collapse:collapse;border:1px solid ${palette.border};border-radius:8px;overflow:hidden;\">\n <tr>\n <td style=\"padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:13px;color:${palette.muted};\">Invoice number</td>\n <td style=\"padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:14px;color:${palette.heading};text-align:right;font-weight:600;\">${invoiceNumber}</td>\n </tr>\n <tr>\n <td style=\"padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:13px;color:${palette.muted};\">Billing period</td>\n <td style=\"padding:12px 16px;border-bottom:1px solid ${palette.border};font-size:14px;color:${palette.heading};text-align:right;\">${periodStart} &mdash; ${periodEnd}</td>\n </tr>\n <tr>\n <td style=\"padding:12px 16px;font-size:13px;color:${palette.muted};\">Amount paid</td>\n <td style=\"padding:12px 16px;font-size:14px;color:#16a34a;text-align:right;font-weight:700;\">${amount} ${currency}</td>\n </tr>\n </table>\n <a href=\"${downloadUrl}\" style=\"display:inline-block;background:#0f172a;color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;\">\n Download PDF receipt &rarr;\n </a>\n <p style=\"margin:0;font-size:13px;color:${palette.muted};\">\n Keep this receipt for your records. Questions about this charge? Reply to this email and the\n ${brandName} billing team will help.\n </p>\n `;\n\n return baseLayout({\n brandName: props.brandName,\n preview: preview(props),\n body,\n });\n}\n\nexport const receiptEmail = { subject, preview, render } as const;\n","/**\n * WelcomeEmail — onboarding welcome message for new accounts.\n *\n * react-email is not installed; this module exposes the same\n * `{ subject, preview, render }` contract using a plain template literal so the\n * catalog can be migrated to React Email later without changing call sites.\n */\n\nimport { baseLayout, escapeHtml, palette } from \"./_layout\";\n\nexport interface WelcomeEmailProps {\n userName: string;\n loginUrl: string;\n brandName: string;\n}\n\nexport function subject(props: WelcomeEmailProps): string {\n return `Welcome to ${props.brandName}`;\n}\n\nexport function preview(_props: WelcomeEmailProps): string {\n return \"Get started with your account\";\n}\n\nexport function render(props: WelcomeEmailProps): string {\n const userName = escapeHtml(props.userName);\n const loginUrl = escapeHtml(props.loginUrl);\n const brandName = escapeHtml(props.brandName);\n\n const body = `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:${palette.heading};\">Welcome, ${userName}!</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:${palette.body};line-height:1.6;\">\n We're glad to have you with ${brandName}. Your account is ready &mdash; sign in to\n explore the dashboard, set up your workspace, and invite the rest of your team.\n </p>\n <a href=\"${loginUrl}\" style=\"display:inline-block;background:${palette.gradient};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;\">\n Get Started &rarr;\n </a>\n <p style=\"margin:0;font-size:13px;color:${palette.muted};\">\n Need help? Reply to this email and a real human from the ${brandName} team will get back to you.\n </p>\n `;\n\n return baseLayout({\n brandName: props.brandName,\n preview: preview(props),\n body,\n });\n}\n\nexport const welcomeEmail = { subject, preview, render } as const;\n","/**\n * React Email-style template catalog.\n *\n * Each template module exports `{ subject, preview, render }` plus a typed\n * Props interface. They render to a complete HTML document via plain template\n * literals (react-email is not installed in this workspace; see AGENTS.md).\n *\n * The legacy `send*Email` helpers in `../index.ts` remain the canonical way to\n * dispatch transactional email. These template modules are the new React-Email\n * shaped surface intended for the mail-preview app and future migration.\n */\n\nexport type { RenderedEmail } from \"./_layout\";\nexport {\n type InvitationEmailProps,\n invitationEmail,\n preview as invitationEmailPreview,\n render as renderInvitationEmail,\n subject as invitationEmailSubject,\n} from \"./invitation\";\nexport {\n type PasswordResetEmailProps,\n passwordResetEmail,\n preview as passwordResetEmailPreview,\n render as renderPasswordResetEmail,\n subject as passwordResetEmailSubject,\n} from \"./password-reset\";\n\nexport {\n preview as receiptEmailPreview,\n type ReceiptEmailProps,\n receiptEmail,\n render as renderReceiptEmail,\n subject as receiptEmailSubject,\n} from \"./receipt\";\nexport {\n preview as welcomeEmailPreview,\n render as renderWelcomeEmail,\n subject as welcomeEmailSubject,\n type WelcomeEmailProps,\n welcomeEmail,\n} from \"./welcome\";\n\nimport { invitationEmail } from \"./invitation\";\nimport { passwordResetEmail } from \"./password-reset\";\nimport { receiptEmail } from \"./receipt\";\nimport { welcomeEmail } from \"./welcome\";\n\n/**\n * Stable shape of a registered template entry for the React Email-style\n * catalog. Distinct from `EMAIL_TEMPLATE_CATALOG` in `../index.ts` which tracks\n * the legacy `send*Email` helper surface.\n */\nexport interface ReactEmailTemplate<P> {\n id: string;\n label: string;\n description: string;\n fileName: string;\n subject: (props: P) => string;\n preview: (props: P) => string;\n render: (props: P) => string;\n}\n\nexport const REACT_EMAIL_TEMPLATES = {\n welcome: {\n id: \"welcome-react\",\n label: \"Welcome (React Email)\",\n description: \"Account onboarding welcome message\",\n fileName: \"welcome-react-email.html\",\n ...welcomeEmail,\n },\n passwordReset: {\n id: \"password-reset\",\n label: \"Password Reset\",\n description: \"Secure password reset link with expiry\",\n fileName: \"password-reset-email.html\",\n ...passwordResetEmail,\n },\n invitation: {\n id: \"invitation\",\n label: \"Invitation\",\n description: \"Workspace invitation with role and expiry\",\n fileName: \"invitation-email.html\",\n ...invitationEmail,\n },\n receipt: {\n id: \"receipt\",\n label: \"Receipt\",\n description: \"Payment receipt with billing period and PDF link\",\n fileName: \"receipt-email.html\",\n ...receiptEmail,\n },\n} as const;\n","/**\n * @nebutra/email — Multi-provider transactional email\n *\n * Provider-agnostic email system with auto-detection:\n * 1. EMAIL_PROVIDER env var (explicit: \"resend\" | \"nodemailer\" | \"console\")\n * 2. RESEND_API_KEY present → Resend\n * 3. SMTP_HOST present → Nodemailer (SMTP)\n * 4. Fallback → Console (dev/test, no API key needed)\n *\n * Usage:\n * import { sendWelcomeEmail, sendApiKeyCreatedEmail } from \"@nebutra/email\";\n * await sendWelcomeEmail({ to: \"user@example.com\", orgName: \"Acme Corp\" });\n *\n * Environment variables:\n * EMAIL_PROVIDER — explicit provider override (optional)\n * EMAIL_FROM — verified sender (e.g. \"Nebutra <noreply@nebutra.ai>\")\n * RESEND_API_KEY — for Resend provider\n * SMTP_HOST — for Nodemailer provider (+ SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE)\n */\n\nimport { getEmailProvider, type SendResult } from \"./provider\";\n\nexport type { EmailProvider, EmailProviderType, SendOptions, SendResult } from \"./provider\";\nexport { getEmailProvider, resetEmailProvider } from \"./provider\";\nexport { ConsoleEmailProvider } from \"./providers/console\";\nexport { NodemailerEmailProvider } from \"./providers/nodemailer\";\nexport { ResendEmailProvider } from \"./providers/resend\";\n\nconst FROM = process.env.EMAIL_FROM ?? \"Nebutra <noreply@nebutra.ai>\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface EmailTemplateCatalogEntry {\n id: string;\n label: string;\n description: string;\n sendHelper: string;\n fileName: string;\n}\n\nexport const EMAIL_TEMPLATE_CATALOG = [\n {\n id: \"welcome\",\n label: \"Welcome\",\n description: \"Workspace provisioning welcome email\",\n sendHelper: \"sendWelcomeEmail\",\n fileName: \"welcome-email.html\",\n },\n {\n id: \"order-confirmation\",\n label: \"Order Confirmation\",\n description: \"Commerce order receipt with line items\",\n sendHelper: \"sendOrderConfirmationEmail\",\n fileName: \"order-confirmation-email.html\",\n },\n {\n id: \"magic-link\",\n label: \"Magic Link\",\n description: \"Passwordless sign-in link\",\n sendHelper: \"sendMagicLinkEmail\",\n fileName: \"magic-link-email.html\",\n },\n {\n id: \"contact-form-received\",\n label: \"Contact Form Received\",\n description: \"Contact form acknowledgement\",\n sendHelper: \"sendContactFormReceivedEmail\",\n fileName: \"contact-form-received-email.html\",\n },\n {\n id: \"license-created\",\n label: \"License Created\",\n description: \"License key delivery email\",\n sendHelper: \"sendLicenseCreatedEmail\",\n fileName: \"license-created-email.html\",\n },\n {\n id: \"welcome-react\",\n label: \"Welcome (React Email)\",\n description: \"Account onboarding welcome message\",\n sendHelper: \"sendWelcomeReactEmail\",\n fileName: \"welcome-react-email.html\",\n },\n {\n id: \"password-reset\",\n label: \"Password Reset\",\n description: \"Secure password reset link with expiry\",\n sendHelper: \"sendPasswordResetEmail\",\n fileName: \"password-reset-email.html\",\n },\n {\n id: \"invitation\",\n label: \"Invitation\",\n description: \"Workspace invitation with role and expiry\",\n sendHelper: \"sendInvitationEmail\",\n fileName: \"invitation-email.html\",\n },\n {\n id: \"receipt\",\n label: \"Receipt\",\n description: \"Payment receipt with billing period and PDF link\",\n sendHelper: \"sendReceiptEmail\",\n fileName: \"receipt-email.html\",\n },\n] as const satisfies readonly EmailTemplateCatalogEntry[];\n\n// ── Core send helper ───────────────────────────────────────────────────────\n\nasync function send(opts: {\n to: string | string[];\n subject: string;\n html: string;\n replyTo?: string;\n tags?: { name: string; value: string }[];\n}): Promise<SendResult> {\n const provider = getEmailProvider();\n return provider.send({\n ...opts,\n from: FROM,\n });\n}\n\n// ── Templates ──────────────────────────────────────────────────────────────\n\nfunction baseLayout(content: string, previewText = \"\"): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"x-apple-disable-message-reformatting\" />\n <title>Nebutra</title>\n <!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->\n</head>\n<body style=\"margin:0;padding:0;background:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;\">\n ${previewText ? `<div style=\"display:none;max-height:0;overflow:hidden;\">${previewText}&nbsp;‌&zwnj;&nbsp;</div>` : \"\"}\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"background:#f8fafc;\">\n <tr><td align=\"center\" style=\"padding:40px 16px;\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\" style=\"background:#ffffff;border-radius:12px;border:1px solid #e2e8f0;overflow:hidden;max-width:600px;width:100%;\">\n <!-- Header -->\n <tr><td style=\"background:linear-gradient(135deg,#0033FE,#0BF1C3);padding:32px 40px;\">\n <h1 style=\"margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;\">Nebutra</h1>\n </td></tr>\n <!-- Body -->\n <tr><td style=\"padding:40px;\">\n ${content}\n </td></tr>\n <!-- Footer -->\n <tr><td style=\"padding:24px 40px;border-top:1px solid #e2e8f0;background:#f8fafc;\">\n <p style=\"margin:0;font-size:12px;color:#94a3b8;text-align:center;\">\n © ${new Date().getFullYear()} Nebutra Intelligence Inc. ·\n <a href=\"https://nebutra.ai/privacy\" style=\"color:#94a3b8;\">Privacy</a> ·\n <a href=\"https://nebutra.ai/unsubscribe\" style=\"color:#94a3b8;\">Unsubscribe</a>\n </p>\n </td></tr>\n </table>\n </td></tr>\n </table>\n</body>\n</html>`;\n}\n\n// ── Email senders ──────────────────────────────────────────────────────────\n\n/**\n * Welcome email sent when a new organization is provisioned.\n */\nexport async function sendWelcomeEmail(opts: {\n to: string;\n firstName: string;\n orgName: string;\n dashboardUrl?: string;\n}): Promise<SendResult> {\n const dashboardUrl = opts.dashboardUrl ?? \"https://app.nebutra.ai\";\n\n const html = baseLayout(\n `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:#0f172a;\">Welcome to Nebutra, ${opts.firstName}!</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Your workspace <strong>${opts.orgName}</strong> is ready. You can now invite team members,\n create API keys, and start building with the Nebutra platform.\n </p>\n <a href=\"${dashboardUrl}\" style=\"display:inline-block;background:linear-gradient(135deg,#0033FE,#0BF1C3);color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;\">\n Open Dashboard →\n </a>\n <p style=\"margin:0;font-size:13px;color:#94a3b8;\">\n If you have questions, reply to this email or visit our <a href=\"https://docs.nebutra.ai\" style=\"color:#0033FE;\">documentation</a>.\n </p>\n `,\n `Welcome to Nebutra — ${opts.orgName} is ready`,\n );\n\n return send({\n to: opts.to,\n subject: `Welcome to Nebutra — ${opts.orgName} is ready`,\n html,\n tags: [{ name: \"type\", value: \"welcome\" }],\n });\n}\n\n/**\n * Order confirmation email sent when an order successfully completes.\n */\nexport async function sendOrderConfirmationEmail(opts: {\n to: string;\n orderId: string;\n totalAmount: number;\n items: Array<{ productId: string; quantity: number | string }>;\n}): Promise<SendResult> {\n const itemsHtml = opts.items\n .map(\n (item) =>\n `<tr>\n <td style=\"padding:12px 0;border-bottom:1px solid #e2e8f0;color:#0f172a;font-size:15px;\">${item.productId}</td>\n <td style=\"padding:12px 0;border-bottom:1px solid #e2e8f0;color:#475569;font-size:15px;text-align:right;\">x${item.quantity}</td>\n </tr>`,\n )\n .join(\"\");\n\n const html = baseLayout(\n `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:#0f172a;\">Order Confirmed!</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Thank you for your order <strong>#${opts.orderId}</strong>. We've received your payment of \n <strong>$${(opts.totalAmount / 100).toFixed(2)}</strong> and your order is now being processed.\n </p>\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin:0 0 24px;border-collapse:collapse;\">\n ${itemsHtml}\n </table>\n <a href=\"https://app.nebutra.ai/orders/${opts.orderId}\" style=\"display:inline-block;background:linear-gradient(135deg,#0033FE,#0BF1C3);color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;\">\n View Order Details →\n </a>\n `,\n `Order #${opts.orderId} Confirmed`,\n );\n\n return send({\n to: opts.to,\n subject: `Order #${opts.orderId} Confirmed`,\n html,\n tags: [{ name: \"type\", value: \"order_confirmation\" }],\n });\n}\n\n/**\n * Shipfast-style Magic Link Email for 1-click Authentication.\n */\nexport async function sendMagicLinkEmail(opts: {\n to: string;\n magicLinkUrl: string;\n}): Promise<SendResult> {\n const html = baseLayout(\n `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:#0f172a;\">Sign in to Nebutra</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Click the button below to sign in securely. No password required.\n </p>\n <a href=\"${opts.magicLinkUrl}\" style=\"display:inline-block;background:linear-gradient(135deg,#0033FE,#0BF1C3);color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:16px;font-weight:600;margin:0 0 24px;text-align:center;width:100%;max-width:280px;\">\n ✨ Sign In Automatically\n </a>\n <p style=\"margin:0;font-size:12px;color:#94a3b8;line-height:1.4;\">\n If you did not request this email, you can safely ignore it.\n This link expires in 15 minutes.\n </p>\n `,\n \"Click here to sign in to your Nebutra account\",\n );\n\n return send({\n to: opts.to,\n subject: \"Sign in to Nebutra ✨\",\n html,\n tags: [{ name: \"type\", value: \"magic_link\" }],\n });\n}\n\n/**\n * Contact Form Receipt (Sent to the User)\n */\nexport async function sendContactFormReceivedEmail(opts: {\n to: string;\n name: string;\n subject: string;\n}): Promise<SendResult> {\n const html = baseLayout(\n `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:#0f172a;\">Message Received</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Hi ${opts.name},\n </p>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Thank you for contacting us regarding \"<strong>${opts.subject}</strong>\". \n We've received your request and our team will get back to you within 1-2 business days.\n </p>\n <p style=\"margin:0;font-size:15px;color:#475569;line-height:1.6;\">\n Best regards,<br/>The Nebutra Team\n </p>\n `,\n \"We have received your message\",\n );\n\n return send({\n to: opts.to,\n subject: \"We've received your message - Nebutra\",\n html,\n tags: [{ name: \"type\", value: \"contact_receipt\" }],\n });\n}\n\n// ── Billing Lifecycle Emails ───────────────────────────────────────────────\n\n/**\n * License created email sent when a user claims their OPC/STARTUP license.\n */\nexport async function sendLicenseCreatedEmail(opts: {\n to: string;\n firstName: string;\n licenseKey: string;\n tier: string;\n}): Promise<SendResult> {\n const html = baseLayout(\n `\n <h2 style=\"margin:0 0 16px;font-size:22px;color:#0f172a;\">Welcome to Nebutra-Sailor! 🚀</h2>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Hi ${opts.firstName}, your <strong>${opts.tier}</strong> license has been successfully generated.\n </p>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n Your official License Key is:\n </p>\n <div style=\"background:#0f172a;border-radius:8px;padding:16px 20px;margin:0 0 24px;overflow-x:auto;\">\n <code style=\"color:#0BF1C3;font-family:'Courier New',monospace;font-size:13px;word-break:break-all;\">${opts.licenseKey}</code>\n </div>\n <p style=\"margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;\">\n To unlock the premium capabilities of the Nebutra CLI locally, run the following command in your terminal:\n </p>\n <div style=\"background:#f1f5f9;border:1px solid #e2e8f0;border-radius:6px;padding:12px;margin:0 0 24px;\">\n <code style=\"color:#334155;font-family:'Courier New',monospace;font-size:13px;\">nebutra license activate ${opts.licenseKey}</code>\n </div>\n <p style=\"margin:0;font-size:13px;color:#94a3b8;\">\n If you have questions, reply to this email or visit our <a href=\"https://docs.nebutra.ai\" style=\"color:#0033FE;\">documentation</a>.\n </p>\n `,\n \"Your Nebutra License Key is Ready\",\n );\n\n return send({\n to: opts.to,\n subject: `Your Nebutra License Key is Ready (${opts.tier})`,\n html,\n tags: [{ name: \"type\", value: \"license_created\" }],\n });\n}\n\n// ── React Email-style templates (welcome, password-reset, invitation, receipt) ─\n\nimport {\n type InvitationEmailProps,\n invitationEmail,\n type PasswordResetEmailProps,\n passwordResetEmail,\n type ReceiptEmailProps,\n receiptEmail,\n type WelcomeEmailProps,\n welcomeEmail,\n} from \"./templates\";\n\nexport {\n type InvitationEmailProps,\n invitationEmail,\n type PasswordResetEmailProps,\n passwordResetEmail,\n REACT_EMAIL_TEMPLATES,\n type ReactEmailTemplate,\n type ReceiptEmailProps,\n type RenderedEmail,\n receiptEmail,\n renderInvitationEmail,\n renderPasswordResetEmail,\n renderReceiptEmail,\n renderWelcomeEmail,\n type WelcomeEmailProps,\n welcomeEmail,\n} from \"./templates\";\n\n/**\n * Send the React Email-style welcome message. Mirrors `WelcomeEmailProps`\n * exactly so callers can compose messages without depending on the legacy\n * `sendWelcomeEmail` signature.\n */\nexport async function sendWelcomeReactEmail(\n opts: { to: string } & WelcomeEmailProps,\n): Promise<SendResult> {\n return send({\n to: opts.to,\n subject: welcomeEmail.subject(opts),\n html: welcomeEmail.render(opts),\n tags: [{ name: \"type\", value: \"welcome_react\" }],\n });\n}\n\n/**\n * Send a password reset email with a tokenized link and expiry note.\n */\nexport async function sendPasswordResetEmail(\n opts: { to: string } & PasswordResetEmailProps,\n): Promise<SendResult> {\n return send({\n to: opts.to,\n subject: passwordResetEmail.subject(opts),\n html: passwordResetEmail.render(opts),\n tags: [{ name: \"type\", value: \"password_reset\" }],\n });\n}\n\n/**\n * Send a workspace invitation with role and accept link.\n */\nexport async function sendInvitationEmail(\n opts: { to: string } & InvitationEmailProps,\n): Promise<SendResult> {\n return send({\n to: opts.to,\n subject: invitationEmail.subject(opts),\n html: invitationEmail.render(opts),\n tags: [{ name: \"type\", value: \"invitation\" }],\n });\n}\n\n/**\n * Send a payment receipt with billing period and PDF download URL.\n */\nexport async function sendReceiptEmail(\n opts: { to: string } & ReceiptEmailProps,\n): Promise<SendResult> {\n return send({\n to: opts.to,\n subject: receiptEmail.subject(opts),\n html: receiptEmail.render(opts),\n tags: [{ name: \"type\", value: \"receipt\" }],\n });\n}\n"],"mappings":"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","names":["preview","subject","preview","render","subject","preview","render","subject","preview","render","baseLayout"]}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@nebutra/email",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Multi-provider transactional email (Resend, Nodemailer, Console)",
5
5
  "private": false,
6
- "license": "AGPL-3.0",
6
+ "license": "MIT",
7
7
  "type": "module",
8
8
  "nebutra": {
9
9
  "featureId": "email",
@@ -21,7 +21,7 @@
21
21
  "resend": "^4.0.1"
22
22
  },
23
23
  "peerDependencies": {
24
- "nodemailer": "^6.9.0"
24
+ "nodemailer": "^8.0.7"
25
25
  },
26
26
  "peerDependenciesMeta": {
27
27
  "nodemailer": {