@nebutra/email 0.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -13,11 +13,13 @@
13
13
  *
14
14
  * Environment variables:
15
15
  * EMAIL_PROVIDER — explicit provider override (optional)
16
- * EMAIL_FROM — verified sender (e.g. "Nebutra <noreply@nebutra.ai>")
16
+ * EMAIL_FROM — verified sender (e.g. getBrandMailFrom() example)
17
17
  * RESEND_API_KEY — for Resend provider
18
18
  * SMTP_HOST — for Nodemailer provider (+ SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_SECURE)
19
19
  */
20
20
 
21
+ import { brand, colors } from "@nebutra/brand/metadata";
22
+ import { getBrandMailFrom, getBrandOrigin } from "@nebutra/brand/metadata-helpers";
21
23
  import { getEmailProvider, type SendResult } from "./provider";
22
24
 
23
25
  export type { EmailProvider, EmailProviderType, SendOptions, SendResult } from "./provider";
@@ -26,7 +28,7 @@ export { ConsoleEmailProvider } from "./providers/console";
26
28
  export { NodemailerEmailProvider } from "./providers/nodemailer";
27
29
  export { ResendEmailProvider } from "./providers/resend";
28
30
 
29
- const FROM = process.env.EMAIL_FROM ?? "Nebutra <noreply@nebutra.ai>";
31
+ const FROM = process.env.EMAIL_FROM ?? getBrandMailFrom();
30
32
 
31
33
  // ── Types ──────────────────────────────────────────────────────────────────
32
34
 
@@ -60,6 +62,13 @@ export const EMAIL_TEMPLATE_CATALOG = [
60
62
  sendHelper: "sendMagicLinkEmail",
61
63
  fileName: "magic-link-email.html",
62
64
  },
65
+ {
66
+ id: "email-change",
67
+ label: "Email Change",
68
+ description: "Confirm a new account email address",
69
+ sendHelper: "sendEmailChangeEmail",
70
+ fileName: "email-change-email.html",
71
+ },
63
72
  {
64
73
  id: "contact-form-received",
65
74
  label: "Contact Form Received",
@@ -129,7 +138,7 @@ function baseLayout(content: string, previewText = ""): string {
129
138
  <meta charset="UTF-8" />
130
139
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
131
140
  <meta name="x-apple-disable-message-reformatting" />
132
- <title>Nebutra</title>
141
+ <title>${brand.name}</title>
133
142
  <!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->
134
143
  </head>
135
144
  <body style="margin:0;padding:0;background:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
@@ -138,8 +147,8 @@ function baseLayout(content: string, previewText = ""): string {
138
147
  <tr><td align="center" style="padding:40px 16px;">
139
148
  <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%;">
140
149
  <!-- Header -->
141
- <tr><td style="background:linear-gradient(135deg,#0033FE,#0BF1C3);padding:32px 40px;">
142
- <h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">Nebutra</h1>
150
+ <tr><td style="background:${colors.primary["500"]};padding:32px 40px;">
151
+ <h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">${brand.name}</h1>
143
152
  </td></tr>
144
153
  <!-- Body -->
145
154
  <tr><td style="padding:40px;">
@@ -148,9 +157,9 @@ function baseLayout(content: string, previewText = ""): string {
148
157
  <!-- Footer -->
149
158
  <tr><td style="padding:24px 40px;border-top:1px solid #e2e8f0;background:#f8fafc;">
150
159
  <p style="margin:0;font-size:12px;color:#94a3b8;text-align:center;">
151
- © ${new Date().getFullYear()} Nebutra Intelligence Inc. ·
152
- <a href="https://nebutra.ai/privacy" style="color:#94a3b8;">Privacy</a> ·
153
- <a href="https://nebutra.ai/unsubscribe" style="color:#94a3b8;">Unsubscribe</a>
160
+ © ${new Date().getFullYear()} ${brand.nameFullEn} ·
161
+ <a href="${getBrandOrigin("landing")}/privacy" style="color:#94a3b8;">Privacy</a> ·
162
+ <a href="${getBrandOrigin("landing")}/unsubscribe" style="color:#94a3b8;">Unsubscribe</a>
154
163
  </p>
155
164
  </td></tr>
156
165
  </table>
@@ -171,28 +180,28 @@ export async function sendWelcomeEmail(opts: {
171
180
  orgName: string;
172
181
  dashboardUrl?: string;
173
182
  }): Promise<SendResult> {
174
- const dashboardUrl = opts.dashboardUrl ?? "https://app.nebutra.ai";
183
+ const dashboardUrl = opts.dashboardUrl ?? getBrandOrigin("app");
175
184
 
176
185
  const html = baseLayout(
177
186
  `
178
- <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to Nebutra, ${opts.firstName}!</h2>
187
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to ${brand.name}, ${opts.firstName}!</h2>
179
188
  <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
180
189
  Your workspace <strong>${opts.orgName}</strong> is ready. You can now invite team members,
181
- create API keys, and start building with the Nebutra platform.
190
+ create API keys, and start building with the ${brand.name} platform.
182
191
  </p>
183
- <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;">
192
+ <a href="${dashboardUrl}" style="display:inline-block;background:${colors.primary["500"]};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
184
193
  Open Dashboard →
185
194
  </a>
186
195
  <p style="margin:0;font-size:13px;color:#94a3b8;">
187
- If you have questions, reply to this email or visit our <a href="https://docs.nebutra.ai" style="color:#0033FE;">documentation</a>.
196
+ If you have questions, reply to this email or visit our <a href="${getBrandOrigin("docs")}" style="color:${colors.primary["500"]};">documentation</a>.
188
197
  </p>
189
198
  `,
190
- `Welcome to Nebutra — ${opts.orgName} is ready`,
199
+ `Welcome to ${brand.name} — ${opts.orgName} is ready`,
191
200
  );
192
201
 
193
202
  return send({
194
203
  to: opts.to,
195
- subject: `Welcome to Nebutra — ${opts.orgName} is ready`,
204
+ subject: `Welcome to ${brand.name} — ${opts.orgName} is ready`,
196
205
  html,
197
206
  tags: [{ name: "type", value: "welcome" }],
198
207
  });
@@ -204,9 +213,19 @@ export async function sendWelcomeEmail(opts: {
204
213
  export async function sendOrderConfirmationEmail(opts: {
205
214
  to: string;
206
215
  orderId: string;
216
+ /** Order total in minor units (integer cents). */
207
217
  totalAmount: number;
218
+ /** ISO 4217 currency code (default "USD"). */
219
+ currency?: string;
220
+ /** BCP 47 locale tag (default "en-US"). */
221
+ locale?: string;
208
222
  items: Array<{ productId: string; quantity: number | string }>;
209
223
  }): Promise<SendResult> {
224
+ const formattedTotal = new Intl.NumberFormat(opts.locale ?? "en-US", {
225
+ style: "currency",
226
+ currency: opts.currency ?? "USD",
227
+ }).format(opts.totalAmount / 100);
228
+
210
229
  const itemsHtml = opts.items
211
230
  .map(
212
231
  (item) =>
@@ -221,13 +240,13 @@ export async function sendOrderConfirmationEmail(opts: {
221
240
  `
222
241
  <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Order Confirmed!</h2>
223
242
  <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
224
- Thank you for your order <strong>#${opts.orderId}</strong>. We've received your payment of
225
- <strong>$${(opts.totalAmount / 100).toFixed(2)}</strong> and your order is now being processed.
243
+ Thank you for your order <strong>#${opts.orderId}</strong>. We've received your payment of
244
+ <strong>${formattedTotal}</strong> and your order is now being processed.
226
245
  </p>
227
246
  <table width="100%" cellpadding="0" cellspacing="0" style="margin:0 0 24px;border-collapse:collapse;">
228
247
  ${itemsHtml}
229
248
  </table>
230
- <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;">
249
+ <a href="${getBrandOrigin("app")}/orders/${opts.orderId}" style="display:inline-block;background:${colors.primary["500"]};color:#ffffff;text-decoration:none;border-radius:8px;padding:12px 24px;font-size:15px;font-weight:600;margin:0 0 24px;">
231
250
  View Order Details →
232
251
  </a>
233
252
  `,
@@ -251,11 +270,11 @@ export async function sendMagicLinkEmail(opts: {
251
270
  }): Promise<SendResult> {
252
271
  const html = baseLayout(
253
272
  `
254
- <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Sign in to Nebutra</h2>
273
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Sign in to ${brand.name}</h2>
255
274
  <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
256
275
  Click the button below to sign in securely. No password required.
257
276
  </p>
258
- <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;">
277
+ <a href="${opts.magicLinkUrl}" style="display:inline-block;background:${colors.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;">
259
278
  ✨ Sign In Automatically
260
279
  </a>
261
280
  <p style="margin:0;font-size:12px;color:#94a3b8;line-height:1.4;">
@@ -263,17 +282,53 @@ export async function sendMagicLinkEmail(opts: {
263
282
  This link expires in 15 minutes.
264
283
  </p>
265
284
  `,
266
- "Click here to sign in to your Nebutra account",
285
+ `Click here to sign in to your ${brand.name} account`,
267
286
  );
268
287
 
269
288
  return send({
270
289
  to: opts.to,
271
- subject: "Sign in to Nebutra ✨",
290
+ subject: `Sign in to ${brand.name} ✨`,
272
291
  html,
273
292
  tags: [{ name: "type", value: "magic_link" }],
274
293
  });
275
294
  }
276
295
 
296
+ /**
297
+ * Email-change verification sent to the *new* address (TODO #126).
298
+ * Confirming the link flips the account email.
299
+ */
300
+ export async function sendEmailChangeEmail(opts: {
301
+ to: string;
302
+ confirmUrl: string;
303
+ /** Optional display name for the new address (defaults to `to`). */
304
+ newEmail?: string;
305
+ }): Promise<SendResult> {
306
+ const recipientEmail = opts.newEmail ?? opts.to;
307
+ const html = baseLayout(
308
+ `
309
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Confirm your new email address</h2>
310
+ <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
311
+ We received a request to change the email on your ${brand.name} account to
312
+ <strong>${recipientEmail}</strong>. Click the button below to confirm. This link expires in 1 hour.
313
+ </p>
314
+ <a href="${opts.confirmUrl}" style="display:inline-block;background:${colors.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;">
315
+ Confirm email change →
316
+ </a>
317
+ <p style="margin:0;font-size:12px;color:#94a3b8;line-height:1.4;">
318
+ If you did not request this change you can safely ignore this email — your address will not be updated.
319
+ </p>
320
+ `,
321
+ "Confirm your new Nebutra email address",
322
+ );
323
+
324
+ return send({
325
+ to: opts.to,
326
+ subject: "Confirm your new Nebutra email address",
327
+ html,
328
+ tags: [{ name: "type", value: "email_change_verification" }],
329
+ });
330
+ }
331
+
277
332
  /**
278
333
  * Contact Form Receipt (Sent to the User)
279
334
  */
@@ -293,7 +348,7 @@ export async function sendContactFormReceivedEmail(opts: {
293
348
  We've received your request and our team will get back to you within 1-2 business days.
294
349
  </p>
295
350
  <p style="margin:0;font-size:15px;color:#475569;line-height:1.6;">
296
- Best regards,<br/>The Nebutra Team
351
+ Best regards,<br/>The ${brand.name} Team
297
352
  </p>
298
353
  `,
299
354
  "We have received your message",
@@ -301,7 +356,7 @@ export async function sendContactFormReceivedEmail(opts: {
301
356
 
302
357
  return send({
303
358
  to: opts.to,
304
- subject: "We've received your message - Nebutra",
359
+ subject: `We've received your message - ${brand.name}`,
305
360
  html,
306
361
  tags: [{ name: "type", value: "contact_receipt" }],
307
362
  });
@@ -320,7 +375,7 @@ export async function sendLicenseCreatedEmail(opts: {
320
375
  }): Promise<SendResult> {
321
376
  const html = baseLayout(
322
377
  `
323
- <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to Nebutra-Sailor! 🚀</h2>
378
+ <h2 style="margin:0 0 16px;font-size:22px;color:#0f172a;">Welcome to ${brand.name}-Sailor! 🚀</h2>
324
379
  <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
325
380
  Hi ${opts.firstName}, your <strong>${opts.tier}</strong> license has been successfully generated.
326
381
  </p>
@@ -328,24 +383,24 @@ export async function sendLicenseCreatedEmail(opts: {
328
383
  Your official License Key is:
329
384
  </p>
330
385
  <div style="background:#0f172a;border-radius:8px;padding:16px 20px;margin:0 0 24px;overflow-x:auto;">
331
- <code style="color:#0BF1C3;font-family:'Courier New',monospace;font-size:13px;word-break:break-all;">${opts.licenseKey}</code>
386
+ <code style="color:${colors.accent["500"]};font-family:'Courier New',monospace;font-size:13px;word-break:break-all;">${opts.licenseKey}</code>
332
387
  </div>
333
388
  <p style="margin:0 0 16px;font-size:15px;color:#475569;line-height:1.6;">
334
- To unlock the premium capabilities of the Nebutra CLI locally, run the following command in your terminal:
389
+ To unlock the premium capabilities of the ${brand.name} CLI locally, run the following command in your terminal:
335
390
  </p>
336
391
  <div style="background:#f1f5f9;border:1px solid #e2e8f0;border-radius:6px;padding:12px;margin:0 0 24px;">
337
392
  <code style="color:#334155;font-family:'Courier New',monospace;font-size:13px;">nebutra license activate ${opts.licenseKey}</code>
338
393
  </div>
339
394
  <p style="margin:0;font-size:13px;color:#94a3b8;">
340
- If you have questions, reply to this email or visit our <a href="https://docs.nebutra.ai" style="color:#0033FE;">documentation</a>.
395
+ If you have questions, reply to this email or visit our <a href="${getBrandOrigin("docs")}" style="color:${colors.primary["500"]};">documentation</a>.
341
396
  </p>
342
397
  `,
343
- "Your Nebutra License Key is Ready",
398
+ `Your ${brand.name} License Key is Ready`,
344
399
  );
345
400
 
346
401
  return send({
347
402
  to: opts.to,
348
- subject: `Your Nebutra License Key is Ready (${opts.tier})`,
403
+ subject: `Your ${brand.name} License Key is Ready (${opts.tier})`,
349
404
  html,
350
405
  tags: [{ name: "type", value: "license_created" }],
351
406
  });
@@ -11,7 +11,7 @@ import {
11
11
  describe("welcome template", () => {
12
12
  const props = {
13
13
  userName: "Ada Lovelace",
14
- loginUrl: "https://app.nebutra.ai/login",
14
+ loginUrl: "https://app.nebutra.com/login",
15
15
  brandName: "Nebutra",
16
16
  };
17
17
 
@@ -28,7 +28,7 @@ describe("welcome template", () => {
28
28
  expect(html.length).toBeGreaterThan(0);
29
29
  expect(html).toContain("Ada Lovelace");
30
30
  expect(html).toContain("Nebutra");
31
- expect(html).toContain("https://app.nebutra.ai/login");
31
+ expect(html).toContain("https://app.nebutra.com/login");
32
32
  expect(html).toMatch(/<!DOCTYPE html>/i);
33
33
  });
34
34
  });
@@ -36,7 +36,7 @@ describe("welcome template", () => {
36
36
  describe("password-reset template", () => {
37
37
  const props = {
38
38
  userName: "Grace Hopper",
39
- resetUrl: "https://app.nebutra.ai/reset?token=abc",
39
+ resetUrl: "https://app.nebutra.com/reset?token=abc",
40
40
  expiresInMinutes: 30,
41
41
  brandName: "Nebutra",
42
42
  };
@@ -52,7 +52,7 @@ describe("password-reset template", () => {
52
52
  it("includes reset url, user name, expiry and disclaimer in HTML body", () => {
53
53
  const html = renderPasswordResetEmail(props);
54
54
  expect(html).toContain("Grace Hopper");
55
- expect(html).toContain("https://app.nebutra.ai/reset?token=abc");
55
+ expect(html).toContain("https://app.nebutra.com/reset?token=abc");
56
56
  expect(html).toContain("30 minutes");
57
57
  expect(html.toLowerCase()).toContain("if this wasn't you");
58
58
  });
@@ -63,7 +63,7 @@ describe("invitation template", () => {
63
63
  inviterName: "Alan Turing",
64
64
  organizationName: "Bletchley Inc.",
65
65
  role: "admin",
66
- acceptUrl: "https://app.nebutra.ai/invites/xyz",
66
+ acceptUrl: "https://app.nebutra.com/invites/xyz",
67
67
  expiresAt: "2026-06-01",
68
68
  brandName: "Nebutra",
69
69
  };
@@ -79,7 +79,7 @@ describe("invitation template", () => {
79
79
  expect(html).toContain("Alan Turing");
80
80
  expect(html).toContain("Bletchley Inc.");
81
81
  expect(html).toContain("admin");
82
- expect(html).toContain("https://app.nebutra.ai/invites/xyz");
82
+ expect(html).toContain("https://app.nebutra.com/invites/xyz");
83
83
  expect(html).toContain("2026-06-01");
84
84
  expect(html).toContain("Sent by Nebutra");
85
85
  });
@@ -93,7 +93,7 @@ describe("receipt template", () => {
93
93
  currency: "USD",
94
94
  periodStart: "2026-04-01",
95
95
  periodEnd: "2026-04-30",
96
- downloadUrl: "https://app.nebutra.ai/receipts/INV-001234.pdf",
96
+ downloadUrl: "https://app.nebutra.com/receipts/INV-001234.pdf",
97
97
  brandName: "Nebutra",
98
98
  };
99
99
 
@@ -109,7 +109,7 @@ describe("receipt template", () => {
109
109
  expect(html).toContain("USD");
110
110
  expect(html).toContain("2026-04-01");
111
111
  expect(html).toContain("2026-04-30");
112
- expect(html).toContain("https://app.nebutra.ai/receipts/INV-001234.pdf");
112
+ expect(html).toContain("https://app.nebutra.com/receipts/INV-001234.pdf");
113
113
  });
114
114
  });
115
115
 
@@ -1,3 +1,5 @@
1
+ import { colors } from "@nebutra/brand/metadata";
2
+ import { getBrandOrigin } from "@nebutra/brand/metadata-helpers";
1
3
  /**
2
4
  * Shared HTML layout for the React Email-style template catalog.
3
5
  *
@@ -15,7 +17,12 @@ export interface RenderedEmail {
15
17
  preview: string;
16
18
  }
17
19
 
18
- const BRAND_GRADIENT = "linear-gradient(135deg,#0033FE,#0BF1C3)";
20
+ /**
21
+ * Email clients do not load app CSS variables. These are identity-locked hex
22
+ * snapshots (VI brand-primary header), not product-chrome skins. Keep in sync with
23
+ * brand assets — do not invent per-template blues.
24
+ */
25
+ const BRAND_HEADER = colors.primary["500"];
19
26
  const COLOR_BG = "#f8fafc";
20
27
  const COLOR_BORDER = "#e2e8f0";
21
28
  const COLOR_HEADING = "#0f172a";
@@ -29,7 +36,7 @@ export interface BaseLayoutOptions {
29
36
  }
30
37
 
31
38
  /**
32
- * Wrap a body fragment with the standard Nebutra-style transactional shell.
39
+ * Wrap a body fragment with the standard brand-style transactional shell.
33
40
  */
34
41
  export function baseLayout({ brandName, preview, body }: BaseLayoutOptions): string {
35
42
  return `<!DOCTYPE html>
@@ -45,7 +52,7 @@ export function baseLayout({ brandName, preview, body }: BaseLayoutOptions): str
45
52
  <table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background:${COLOR_BG};">
46
53
  <tr><td align="center" style="padding:40px 16px;">
47
54
  <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%;">
48
- <tr><td style="background:${BRAND_GRADIENT};padding:32px 40px;">
55
+ <tr><td style="background:${BRAND_HEADER};padding:32px 40px;">
49
56
  <h1 style="margin:0;color:#ffffff;font-size:24px;font-weight:700;letter-spacing:-0.5px;">${escapeHtml(brandName)}</h1>
50
57
  </td></tr>
51
58
  <tr><td style="padding:40px;">
@@ -54,8 +61,8 @@ export function baseLayout({ brandName, preview, body }: BaseLayoutOptions): str
54
61
  <tr><td style="padding:24px 40px;border-top:1px solid ${COLOR_BORDER};background:${COLOR_BG};">
55
62
  <p style="margin:0;font-size:12px;color:${COLOR_MUTED};text-align:center;">
56
63
  &copy; ${new Date().getFullYear()} ${escapeHtml(brandName)} &middot;
57
- <a href="https://nebutra.ai/privacy" style="color:${COLOR_MUTED};">Privacy</a> &middot;
58
- <a href="https://nebutra.ai/unsubscribe" style="color:${COLOR_MUTED};">Unsubscribe</a>
64
+ <a href="${getBrandOrigin("landing")}/privacy" style="color:${COLOR_MUTED};">Privacy</a> &middot;
65
+ <a href="${getBrandOrigin("landing")}/unsubscribe" style="color:${COLOR_MUTED};">Unsubscribe</a>
59
66
  </p>
60
67
  </td></tr>
61
68
  </table>
@@ -66,7 +73,9 @@ export function baseLayout({ brandName, preview, body }: BaseLayoutOptions): str
66
73
  }
67
74
 
68
75
  export const palette = {
69
- gradient: BRAND_GRADIENT,
76
+ /** @deprecated alias of solid identity header — not a product skin */
77
+ gradient: BRAND_HEADER,
78
+ header: BRAND_HEADER,
70
79
  background: COLOR_BG,
71
80
  border: COLOR_BORDER,
72
81
  heading: COLOR_HEADING,
@@ -6,6 +6,7 @@
6
6
  * catalog can be migrated to React Email later without changing call sites.
7
7
  */
8
8
 
9
+ import { colors } from "@nebutra/brand/metadata";
9
10
  import { baseLayout, escapeHtml, palette } from "./_layout";
10
11
 
11
12
  export interface InvitationEmailProps {
@@ -41,7 +42,7 @@ export function render(props: InvitationEmailProps): string {
41
42
  </p>
42
43
  <p style="margin:0 0 24px;font-size:14px;color:${palette.body};">
43
44
  Role:
44
- <span style="display:inline-block;background:#e0f2fe;color:#0033FE;padding:4px 10px;border-radius:999px;font-size:13px;font-weight:600;margin-left:6px;">
45
+ <span style="display:inline-block;background:#e0f2fe;color:${colors.primary["500"]};padding:4px 10px;border-radius:999px;font-size:13px;font-weight:600;margin-left:6px;">
45
46
  ${role}
46
47
  </span>
47
48
  </p>