@nebutra/email 0.1.1 → 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/.turbo/turbo-build.log +14 -14
- package/.turbo/turbo-test.log +9 -9
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -676
- package/README.md +21 -4
- package/dist/index.d.ts +23 -2
- package/dist/index.js +83 -35
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/provider.test.ts +5 -5
- package/src/index.ts +85 -30
- package/src/templates/_layout.ts +15 -6
- package/src/templates/invitation.tsx +2 -1
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# @nebutra/email
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
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
|
package/dist/index.d.ts
CHANGED
|
@@ -251,7 +251,7 @@ declare const REACT_EMAIL_TEMPLATES: {
|
|
|
251
251
|
*
|
|
252
252
|
* Environment variables:
|
|
253
253
|
* EMAIL_PROVIDER — explicit provider override (optional)
|
|
254
|
-
* EMAIL_FROM — verified sender (e.g.
|
|
254
|
+
* EMAIL_FROM — verified sender (e.g. getBrandMailFrom() example)
|
|
255
255
|
* RESEND_API_KEY — for Resend provider
|
|
256
256
|
* SMTP_HOST — for Nodemailer provider (+ SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE)
|
|
257
257
|
*/
|
|
@@ -281,6 +281,12 @@ declare const EMAIL_TEMPLATE_CATALOG: readonly [{
|
|
|
281
281
|
readonly description: "Passwordless sign-in link";
|
|
282
282
|
readonly sendHelper: "sendMagicLinkEmail";
|
|
283
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";
|
|
284
290
|
}, {
|
|
285
291
|
readonly id: "contact-form-received";
|
|
286
292
|
readonly label: "Contact Form Received";
|
|
@@ -333,7 +339,12 @@ declare function sendWelcomeEmail(opts: {
|
|
|
333
339
|
declare function sendOrderConfirmationEmail(opts: {
|
|
334
340
|
to: string;
|
|
335
341
|
orderId: string;
|
|
342
|
+
/** Order total in minor units (integer cents). */
|
|
336
343
|
totalAmount: number;
|
|
344
|
+
/** ISO 4217 currency code (default "USD"). */
|
|
345
|
+
currency?: string;
|
|
346
|
+
/** BCP 47 locale tag (default "en-US"). */
|
|
347
|
+
locale?: string;
|
|
337
348
|
items: Array<{
|
|
338
349
|
productId: string;
|
|
339
350
|
quantity: number | string;
|
|
@@ -346,6 +357,16 @@ declare function sendMagicLinkEmail(opts: {
|
|
|
346
357
|
to: string;
|
|
347
358
|
magicLinkUrl: string;
|
|
348
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>;
|
|
349
370
|
/**
|
|
350
371
|
* Contact Form Receipt (Sent to the User)
|
|
351
372
|
*/
|
|
@@ -391,4 +412,4 @@ declare function sendReceiptEmail(opts: {
|
|
|
391
412
|
to: string;
|
|
392
413
|
} & ReceiptEmailProps): Promise<SendResult>;
|
|
393
414
|
|
|
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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { brand, colors as colors3 } from "@nebutra/brand/metadata";
|
|
3
|
+
import { getBrandMailFrom, getBrandOrigin as getBrandOrigin2 } from "@nebutra/brand/metadata-helpers";
|
|
4
|
+
|
|
1
5
|
// src/providers/console.ts
|
|
2
6
|
var counter = 0;
|
|
3
7
|
var ConsoleEmailProvider = class {
|
|
@@ -134,8 +138,13 @@ function resetEmailProvider() {
|
|
|
134
138
|
cachedProvider = void 0;
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
// src/templates/invitation.tsx
|
|
142
|
+
import { colors as colors2 } from "@nebutra/brand/metadata";
|
|
143
|
+
|
|
137
144
|
// src/templates/_layout.ts
|
|
138
|
-
|
|
145
|
+
import { colors } from "@nebutra/brand/metadata";
|
|
146
|
+
import { getBrandOrigin } from "@nebutra/brand/metadata-helpers";
|
|
147
|
+
var BRAND_HEADER = colors.primary["500"];
|
|
139
148
|
var COLOR_BG = "#f8fafc";
|
|
140
149
|
var COLOR_BORDER = "#e2e8f0";
|
|
141
150
|
var COLOR_HEADING = "#0f172a";
|
|
@@ -155,7 +164,7 @@ function baseLayout({ brandName, preview: preview5, body }) {
|
|
|
155
164
|
<table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background:${COLOR_BG};">
|
|
156
165
|
<tr><td align="center" style="padding:40px 16px;">
|
|
157
166
|
<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:${
|
|
167
|
+
<tr><td style="background:${BRAND_HEADER};padding:32px 40px;">
|
|
159
168
|
<h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">${escapeHtml(brandName)}</h1>
|
|
160
169
|
</td></tr>
|
|
161
170
|
<tr><td style="padding:40px;">
|
|
@@ -164,8 +173,8 @@ function baseLayout({ brandName, preview: preview5, body }) {
|
|
|
164
173
|
<tr><td style="padding:24px 40px;border-top:1px solid ${COLOR_BORDER};background:${COLOR_BG};">
|
|
165
174
|
<p style="margin:0;font-size:12px;color:${COLOR_MUTED};text-align:center;">
|
|
166
175
|
© ${(/* @__PURE__ */ new Date()).getFullYear()} ${escapeHtml(brandName)} ·
|
|
167
|
-
<a href="
|
|
168
|
-
<a href="
|
|
176
|
+
<a href="${getBrandOrigin("landing")}/privacy" style="color:${COLOR_MUTED};">Privacy</a> ·
|
|
177
|
+
<a href="${getBrandOrigin("landing")}/unsubscribe" style="color:${COLOR_MUTED};">Unsubscribe</a>
|
|
169
178
|
</p>
|
|
170
179
|
</td></tr>
|
|
171
180
|
</table>
|
|
@@ -175,7 +184,9 @@ function baseLayout({ brandName, preview: preview5, body }) {
|
|
|
175
184
|
</html>`;
|
|
176
185
|
}
|
|
177
186
|
var palette = {
|
|
178
|
-
|
|
187
|
+
/** @deprecated alias of solid identity header — not a product skin */
|
|
188
|
+
gradient: BRAND_HEADER,
|
|
189
|
+
header: BRAND_HEADER,
|
|
179
190
|
background: COLOR_BG,
|
|
180
191
|
border: COLOR_BORDER,
|
|
181
192
|
heading: COLOR_HEADING,
|
|
@@ -208,7 +219,7 @@ function render(props) {
|
|
|
208
219
|
</p>
|
|
209
220
|
<p style="margin:0 0 24px;font-size:14px;color:${palette.body};">
|
|
210
221
|
Role:
|
|
211
|
-
<span style="display:inline-block;background:#e0f2fe;color
|
|
222
|
+
<span style="display:inline-block;background:#e0f2fe;color:${colors2.primary["500"]};padding:4px 10px;border-radius:999px;font-size:13px;font-weight:600;margin-left:6px;">
|
|
212
223
|
${role}
|
|
213
224
|
</span>
|
|
214
225
|
</p>
|
|
@@ -386,7 +397,7 @@ var REACT_EMAIL_TEMPLATES = {
|
|
|
386
397
|
};
|
|
387
398
|
|
|
388
399
|
// src/index.ts
|
|
389
|
-
var FROM = process.env.EMAIL_FROM ??
|
|
400
|
+
var FROM = process.env.EMAIL_FROM ?? getBrandMailFrom();
|
|
390
401
|
var EMAIL_TEMPLATE_CATALOG = [
|
|
391
402
|
{
|
|
392
403
|
id: "welcome",
|
|
@@ -409,6 +420,13 @@ var EMAIL_TEMPLATE_CATALOG = [
|
|
|
409
420
|
sendHelper: "sendMagicLinkEmail",
|
|
410
421
|
fileName: "magic-link-email.html"
|
|
411
422
|
},
|
|
423
|
+
{
|
|
424
|
+
id: "email-change",
|
|
425
|
+
label: "Email Change",
|
|
426
|
+
description: "Confirm a new account email address",
|
|
427
|
+
sendHelper: "sendEmailChangeEmail",
|
|
428
|
+
fileName: "email-change-email.html"
|
|
429
|
+
},
|
|
412
430
|
{
|
|
413
431
|
id: "contact-form-received",
|
|
414
432
|
label: "Contact Form Received",
|
|
@@ -466,7 +484,7 @@ function baseLayout2(content, previewText = "") {
|
|
|
466
484
|
<meta charset="UTF-8" />
|
|
467
485
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
468
486
|
<meta name="x-apple-disable-message-reformatting" />
|
|
469
|
-
<title
|
|
487
|
+
<title>${brand.name}</title>
|
|
470
488
|
<!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->
|
|
471
489
|
</head>
|
|
472
490
|
<body style="margin:0;padding:0;background:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
@@ -475,8 +493,8 @@ function baseLayout2(content, previewText = "") {
|
|
|
475
493
|
<tr><td align="center" style="padding:40px 16px;">
|
|
476
494
|
<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
495
|
<!-- Header -->
|
|
478
|
-
<tr><td style="background
|
|
479
|
-
<h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;"
|
|
496
|
+
<tr><td style="background:${colors3.primary["500"]};padding:32px 40px;">
|
|
497
|
+
<h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">${brand.name}</h1>
|
|
480
498
|
</td></tr>
|
|
481
499
|
<!-- Body -->
|
|
482
500
|
<tr><td style="padding:40px;">
|
|
@@ -485,9 +503,9 @@ function baseLayout2(content, previewText = "") {
|
|
|
485
503
|
<!-- Footer -->
|
|
486
504
|
<tr><td style="padding:24px 40px;border-top:1px solid #e2e8f0;background:#f8fafc;">
|
|
487
505
|
<p style="margin:0;font-size:12px;color:#94a3b8;text-align:center;">
|
|
488
|
-
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()}
|
|
489
|
-
<a href="
|
|
490
|
-
<a href="
|
|
506
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${brand.nameFullEn} \xB7
|
|
507
|
+
<a href="${getBrandOrigin2("landing")}/privacy" style="color:#94a3b8;">Privacy</a> \xB7
|
|
508
|
+
<a href="${getBrandOrigin2("landing")}/unsubscribe" style="color:#94a3b8;">Unsubscribe</a>
|
|
491
509
|
</p>
|
|
492
510
|
</td></tr>
|
|
493
511
|
</table>
|
|
@@ -497,31 +515,35 @@ function baseLayout2(content, previewText = "") {
|
|
|
497
515
|
</html>`;
|
|
498
516
|
}
|
|
499
517
|
async function sendWelcomeEmail(opts) {
|
|
500
|
-
const dashboardUrl = opts.dashboardUrl ?? "
|
|
518
|
+
const dashboardUrl = opts.dashboardUrl ?? getBrandOrigin2("app");
|
|
501
519
|
const html = baseLayout2(
|
|
502
520
|
`
|
|
503
|
-
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to
|
|
521
|
+
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to ${brand.name}, ${opts.firstName}!</h2>
|
|
504
522
|
<p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
|
|
505
523
|
Your workspace <strong>${opts.orgName}</strong> is ready. You can now invite team members,
|
|
506
|
-
create API keys, and start building with the
|
|
524
|
+
create API keys, and start building with the ${brand.name} platform.
|
|
507
525
|
</p>
|
|
508
|
-
<a href="${dashboardUrl}" style="display:inline-block;background
|
|
526
|
+
<a href="${dashboardUrl}" style="display:inline-block;background:${colors3.primary["500"]};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
|
|
509
527
|
Open Dashboard \u2192
|
|
510
528
|
</a>
|
|
511
529
|
<p style="margin:0;font-size:13px;color:#94a3b8;">
|
|
512
|
-
If you have questions, reply to this email or visit our <a href="
|
|
530
|
+
If you have questions, reply to this email or visit our <a href="${getBrandOrigin2("docs")}" style="color:${colors3.primary["500"]};">documentation</a>.
|
|
513
531
|
</p>
|
|
514
532
|
`,
|
|
515
|
-
`Welcome to
|
|
533
|
+
`Welcome to ${brand.name} \u2014 ${opts.orgName} is ready`
|
|
516
534
|
);
|
|
517
535
|
return send({
|
|
518
536
|
to: opts.to,
|
|
519
|
-
subject: `Welcome to
|
|
537
|
+
subject: `Welcome to ${brand.name} \u2014 ${opts.orgName} is ready`,
|
|
520
538
|
html,
|
|
521
539
|
tags: [{ name: "type", value: "welcome" }]
|
|
522
540
|
});
|
|
523
541
|
}
|
|
524
542
|
async function sendOrderConfirmationEmail(opts) {
|
|
543
|
+
const formattedTotal = new Intl.NumberFormat(opts.locale ?? "en-US", {
|
|
544
|
+
style: "currency",
|
|
545
|
+
currency: opts.currency ?? "USD"
|
|
546
|
+
}).format(opts.totalAmount / 100);
|
|
525
547
|
const itemsHtml = opts.items.map(
|
|
526
548
|
(item) => `<tr>
|
|
527
549
|
<td style="padding:12px 0;border-bottom:1px solid #e2e8f0;color:#0f172a;font-size:15px;">${item.productId}</td>
|
|
@@ -532,13 +554,13 @@ async function sendOrderConfirmationEmail(opts) {
|
|
|
532
554
|
`
|
|
533
555
|
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Order Confirmed!</h2>
|
|
534
556
|
<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
|
|
557
|
+
Thank you for your order <strong>#${opts.orderId}</strong>. We've received your payment of
|
|
558
|
+
<strong>${formattedTotal}</strong> and your order is now being processed.
|
|
537
559
|
</p>
|
|
538
560
|
<table width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 24px;border-collapse:collapse;">
|
|
539
561
|
${itemsHtml}
|
|
540
562
|
</table>
|
|
541
|
-
<a href="
|
|
563
|
+
<a href="${getBrandOrigin2("app")}/orders/${opts.orderId}" style="display:inline-block;background:${colors3.primary["500"]};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
|
|
542
564
|
View Order Details \u2192
|
|
543
565
|
</a>
|
|
544
566
|
`,
|
|
@@ -554,11 +576,11 @@ async function sendOrderConfirmationEmail(opts) {
|
|
|
554
576
|
async function sendMagicLinkEmail(opts) {
|
|
555
577
|
const html = baseLayout2(
|
|
556
578
|
`
|
|
557
|
-
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Sign in to
|
|
579
|
+
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Sign in to ${brand.name}</h2>
|
|
558
580
|
<p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
|
|
559
581
|
Click the button below to sign in securely. No password required.
|
|
560
582
|
</p>
|
|
561
|
-
<a href="${opts.magicLinkUrl}" style="display:inline-block;background
|
|
583
|
+
<a href="${opts.magicLinkUrl}" style="display:inline-block;background:${colors3.primary["500"]};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
584
|
\u2728 Sign In Automatically
|
|
563
585
|
</a>
|
|
564
586
|
<p style="margin:0;font-size:12px;color:#94a3b8;line-height:1.4;">
|
|
@@ -566,15 +588,40 @@ async function sendMagicLinkEmail(opts) {
|
|
|
566
588
|
This link expires in 15 minutes.
|
|
567
589
|
</p>
|
|
568
590
|
`,
|
|
569
|
-
|
|
591
|
+
`Click here to sign in to your ${brand.name} account`
|
|
570
592
|
);
|
|
571
593
|
return send({
|
|
572
594
|
to: opts.to,
|
|
573
|
-
subject:
|
|
595
|
+
subject: `Sign in to ${brand.name} \u2728`,
|
|
574
596
|
html,
|
|
575
597
|
tags: [{ name: "type", value: "magic_link" }]
|
|
576
598
|
});
|
|
577
599
|
}
|
|
600
|
+
async function sendEmailChangeEmail(opts) {
|
|
601
|
+
const recipientEmail = opts.newEmail ?? opts.to;
|
|
602
|
+
const html = baseLayout2(
|
|
603
|
+
`
|
|
604
|
+
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Confirm your new email address</h2>
|
|
605
|
+
<p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
|
|
606
|
+
We received a request to change the email on your ${brand.name} account to
|
|
607
|
+
<strong>${recipientEmail}</strong>. Click the button below to confirm. This link expires in 1 hour.
|
|
608
|
+
</p>
|
|
609
|
+
<a href="${opts.confirmUrl}" style="display:inline-block;background:${colors3.primary["500"]};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;">
|
|
610
|
+
Confirm email change \u2192
|
|
611
|
+
</a>
|
|
612
|
+
<p style="margin:0;font-size:12px;color:#94a3b8;line-height:1.4;">
|
|
613
|
+
If you did not request this change you can safely ignore this email \u2014 your address will not be updated.
|
|
614
|
+
</p>
|
|
615
|
+
`,
|
|
616
|
+
"Confirm your new Nebutra email address"
|
|
617
|
+
);
|
|
618
|
+
return send({
|
|
619
|
+
to: opts.to,
|
|
620
|
+
subject: "Confirm your new Nebutra email address",
|
|
621
|
+
html,
|
|
622
|
+
tags: [{ name: "type", value: "email_change_verification" }]
|
|
623
|
+
});
|
|
624
|
+
}
|
|
578
625
|
async function sendContactFormReceivedEmail(opts) {
|
|
579
626
|
const html = baseLayout2(
|
|
580
627
|
`
|
|
@@ -587,14 +634,14 @@ async function sendContactFormReceivedEmail(opts) {
|
|
|
587
634
|
We've received your request and our team will get back to you within 1-2 business days.
|
|
588
635
|
</p>
|
|
589
636
|
<p style="margin:0;font-size:15px;color:#475569;line-height:1.6;">
|
|
590
|
-
Best regards,<br/>The
|
|
637
|
+
Best regards,<br/>The ${brand.name} Team
|
|
591
638
|
</p>
|
|
592
639
|
`,
|
|
593
640
|
"We have received your message"
|
|
594
641
|
);
|
|
595
642
|
return send({
|
|
596
643
|
to: opts.to,
|
|
597
|
-
subject:
|
|
644
|
+
subject: `We've received your message - ${brand.name}`,
|
|
598
645
|
html,
|
|
599
646
|
tags: [{ name: "type", value: "contact_receipt" }]
|
|
600
647
|
});
|
|
@@ -602,7 +649,7 @@ async function sendContactFormReceivedEmail(opts) {
|
|
|
602
649
|
async function sendLicenseCreatedEmail(opts) {
|
|
603
650
|
const html = baseLayout2(
|
|
604
651
|
`
|
|
605
|
-
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to
|
|
652
|
+
<h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to ${brand.name}-Sailor! \u{1F680}</h2>
|
|
606
653
|
<p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
|
|
607
654
|
Hi ${opts.firstName}, your <strong>${opts.tier}</strong> license has been successfully generated.
|
|
608
655
|
</p>
|
|
@@ -610,23 +657,23 @@ async function sendLicenseCreatedEmail(opts) {
|
|
|
610
657
|
Your official License Key is:
|
|
611
658
|
</p>
|
|
612
659
|
<div style="background:#0f172a;border-radius:8px;padding:16px 20px;margin:0 0 24px;overflow-x:auto;">
|
|
613
|
-
<code style="color
|
|
660
|
+
<code style="color:${colors3.accent["500"]};font-family:'Courier New',monospace;font-size:13px;word-break:break-all;">${opts.licenseKey}</code>
|
|
614
661
|
</div>
|
|
615
662
|
<p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
|
|
616
|
-
To unlock the premium capabilities of the
|
|
663
|
+
To unlock the premium capabilities of the ${brand.name} CLI locally, run the following command in your terminal:
|
|
617
664
|
</p>
|
|
618
665
|
<div style="background:#f1f5f9;border:1px solid #e2e8f0;border-radius:6px;padding:12px;margin:0 0 24px;">
|
|
619
666
|
<code style="color:#334155;font-family:'Courier New',monospace;font-size:13px;">nebutra license activate ${opts.licenseKey}</code>
|
|
620
667
|
</div>
|
|
621
668
|
<p style="margin:0;font-size:13px;color:#94a3b8;">
|
|
622
|
-
If you have questions, reply to this email or visit our <a href="
|
|
669
|
+
If you have questions, reply to this email or visit our <a href="${getBrandOrigin2("docs")}" style="color:${colors3.primary["500"]};">documentation</a>.
|
|
623
670
|
</p>
|
|
624
671
|
`,
|
|
625
|
-
|
|
672
|
+
`Your ${brand.name} License Key is Ready`
|
|
626
673
|
);
|
|
627
674
|
return send({
|
|
628
675
|
to: opts.to,
|
|
629
|
-
subject: `Your
|
|
676
|
+
subject: `Your ${brand.name} License Key is Ready (${opts.tier})`,
|
|
630
677
|
html,
|
|
631
678
|
tags: [{ name: "type", value: "license_created" }]
|
|
632
679
|
});
|
|
@@ -679,6 +726,7 @@ export {
|
|
|
679
726
|
render4 as renderWelcomeEmail,
|
|
680
727
|
resetEmailProvider,
|
|
681
728
|
sendContactFormReceivedEmail,
|
|
729
|
+
sendEmailChangeEmail,
|
|
682
730
|
sendInvitationEmail,
|
|
683
731
|
sendLicenseCreatedEmail,
|
|
684
732
|
sendMagicLinkEmail,
|