@nebutra/email 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # @nebutra/email
2
2
 
3
- > Transactional email service powered by Resend with branded HTML templates.
3
+ Multi-provider transactional email for Nebutra products.
4
+
5
+ The package auto-detects Resend, Nodemailer SMTP, or a console development
6
+ provider, then sends branded HTML templates through a single API.
4
7
 
5
8
  ## Installation
6
9
 
7
10
  ```bash
8
- # Internal monorepo dependency
9
- pnpm add @nebutra/email@workspace:*
11
+ pnpm add @nebutra/email
10
12
  ```
11
13
 
12
14
  ## Usage
@@ -53,5 +55,20 @@ await sendApiKeyCreatedEmail({
53
55
 
54
56
  | Environment Variable | Description |
55
57
  |---------------------|-------------|
58
+ | `EMAIL_PROVIDER` | Optional explicit provider: `resend`, `nodemailer`, or `console` |
56
59
  | `RESEND_API_KEY` | Resend API key from https://resend.com/api-keys |
57
- | `EMAIL_FROM` | Verified sender address (default: `Nebutra <noreply@nebutra.ai>`) |
60
+ | `SMTP_HOST` | Enables Nodemailer SMTP provider when Resend is not configured |
61
+ | `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` / `SMTP_SECURE` | SMTP provider settings |
62
+ | `EMAIL_FROM` | Verified sender address (default: `Nebutra <noreply@nebutra.com>`) |
63
+
64
+ ## Providers
65
+
66
+ | Provider | Selection |
67
+ | --- | --- |
68
+ | `resend` | `EMAIL_PROVIDER=resend` or `RESEND_API_KEY` present |
69
+ | `nodemailer` | `EMAIL_PROVIDER=nodemailer` or `SMTP_HOST` present |
70
+ | `console` | Explicit `EMAIL_PROVIDER=console` or development/test fallback |
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,415 @@
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. getBrandMailFrom() example)
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: "email-change";
286
+ readonly label: "Email Change";
287
+ readonly description: "Confirm a new account email address";
288
+ readonly sendHelper: "sendEmailChangeEmail";
289
+ readonly fileName: "email-change-email.html";
290
+ }, {
291
+ readonly id: "contact-form-received";
292
+ readonly label: "Contact Form Received";
293
+ readonly description: "Contact form acknowledgement";
294
+ readonly sendHelper: "sendContactFormReceivedEmail";
295
+ readonly fileName: "contact-form-received-email.html";
296
+ }, {
297
+ readonly id: "license-created";
298
+ readonly label: "License Created";
299
+ readonly description: "License key delivery email";
300
+ readonly sendHelper: "sendLicenseCreatedEmail";
301
+ readonly fileName: "license-created-email.html";
302
+ }, {
303
+ readonly id: "welcome-react";
304
+ readonly label: "Welcome (React Email)";
305
+ readonly description: "Account onboarding welcome message";
306
+ readonly sendHelper: "sendWelcomeReactEmail";
307
+ readonly fileName: "welcome-react-email.html";
308
+ }, {
309
+ readonly id: "password-reset";
310
+ readonly label: "Password Reset";
311
+ readonly description: "Secure password reset link with expiry";
312
+ readonly sendHelper: "sendPasswordResetEmail";
313
+ readonly fileName: "password-reset-email.html";
314
+ }, {
315
+ readonly id: "invitation";
316
+ readonly label: "Invitation";
317
+ readonly description: "Workspace invitation with role and expiry";
318
+ readonly sendHelper: "sendInvitationEmail";
319
+ readonly fileName: "invitation-email.html";
320
+ }, {
321
+ readonly id: "receipt";
322
+ readonly label: "Receipt";
323
+ readonly description: "Payment receipt with billing period and PDF link";
324
+ readonly sendHelper: "sendReceiptEmail";
325
+ readonly fileName: "receipt-email.html";
326
+ }];
327
+ /**
328
+ * Welcome email sent when a new organization is provisioned.
329
+ */
330
+ declare function sendWelcomeEmail(opts: {
331
+ to: string;
332
+ firstName: string;
333
+ orgName: string;
334
+ dashboardUrl?: string;
335
+ }): Promise<SendResult>;
336
+ /**
337
+ * Order confirmation email sent when an order successfully completes.
338
+ */
339
+ declare function sendOrderConfirmationEmail(opts: {
340
+ to: string;
341
+ orderId: string;
342
+ /** Order total in minor units (integer cents). */
343
+ totalAmount: number;
344
+ /** ISO 4217 currency code (default "USD"). */
345
+ currency?: string;
346
+ /** BCP 47 locale tag (default "en-US"). */
347
+ locale?: string;
348
+ items: Array<{
349
+ productId: string;
350
+ quantity: number | string;
351
+ }>;
352
+ }): Promise<SendResult>;
353
+ /**
354
+ * Shipfast-style Magic Link Email for 1-click Authentication.
355
+ */
356
+ declare function sendMagicLinkEmail(opts: {
357
+ to: string;
358
+ magicLinkUrl: string;
359
+ }): Promise<SendResult>;
360
+ /**
361
+ * Email-change verification sent to the *new* address (TODO #126).
362
+ * Confirming the link flips the account email.
363
+ */
364
+ declare function sendEmailChangeEmail(opts: {
365
+ to: string;
366
+ confirmUrl: string;
367
+ /** Optional display name for the new address (defaults to `to`). */
368
+ newEmail?: string;
369
+ }): Promise<SendResult>;
370
+ /**
371
+ * Contact Form Receipt (Sent to the User)
372
+ */
373
+ declare function sendContactFormReceivedEmail(opts: {
374
+ to: string;
375
+ name: string;
376
+ subject: string;
377
+ }): Promise<SendResult>;
378
+ /**
379
+ * License created email sent when a user claims their OPC/STARTUP license.
380
+ */
381
+ declare function sendLicenseCreatedEmail(opts: {
382
+ to: string;
383
+ firstName: string;
384
+ licenseKey: string;
385
+ tier: string;
386
+ }): Promise<SendResult>;
387
+
388
+ /**
389
+ * Send the React Email-style welcome message. Mirrors `WelcomeEmailProps`
390
+ * exactly so callers can compose messages without depending on the legacy
391
+ * `sendWelcomeEmail` signature.
392
+ */
393
+ declare function sendWelcomeReactEmail(opts: {
394
+ to: string;
395
+ } & WelcomeEmailProps): Promise<SendResult>;
396
+ /**
397
+ * Send a password reset email with a tokenized link and expiry note.
398
+ */
399
+ declare function sendPasswordResetEmail(opts: {
400
+ to: string;
401
+ } & PasswordResetEmailProps): Promise<SendResult>;
402
+ /**
403
+ * Send a workspace invitation with role and accept link.
404
+ */
405
+ declare function sendInvitationEmail(opts: {
406
+ to: string;
407
+ } & InvitationEmailProps): Promise<SendResult>;
408
+ /**
409
+ * Send a payment receipt with billing period and PDF download URL.
410
+ */
411
+ declare function sendReceiptEmail(opts: {
412
+ to: string;
413
+ } & ReceiptEmailProps): Promise<SendResult>;
414
+
415
+ 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, sendEmailChangeEmail, sendInvitationEmail, sendLicenseCreatedEmail, sendMagicLinkEmail, sendOrderConfirmationEmail, sendPasswordResetEmail, sendReceiptEmail, sendWelcomeEmail, sendWelcomeReactEmail, welcomeEmail };