From 3249b0dc8c00220b74e1008f94812cc02f6313c1 Mon Sep 17 00:00:00 2001
From: Elias Schneider
Date: Sun, 20 Sep 2026 16:07:29 +0200
Subject: [PATCH] feat: update the design of the email templates
---
backend/internal/email/module.go | 2 +
backend/internal/email/module_test.go | 2 +-
.../api-key-expiring-soon_html.tmpl | 2 +-
.../api-key-expiring-soon_text.tmpl | 12 +-
.../email-verification_html.tmpl | 2 +-
.../email-verification_text.tmpl | 12 +-
.../login-with-new-device_html.tmpl | 2 +-
.../login-with-new-device_text.tmpl | 23 +-
.../email-templates/one-time-access_html.tmpl | 2 +-
.../email-templates/one-time-access_text.tmpl | 12 +-
.../resources/email-templates/test_html.tmpl | 2 +-
.../resources/email-templates/test_text.tmpl | 5 +-
email-templates/build.ts | 9 +-
email-templates/components/base-template.tsx | 136 ++--
email-templates/components/button.tsx | 48 +-
email-templates/components/card-header.tsx | 46 +-
email-templates/components/code-box.tsx | 39 ++
email-templates/components/details-list.tsx | 54 ++
email-templates/components/text.tsx | 29 +
email-templates/components/theme.ts | 25 +
.../emails/api-key-expiring-soon.tsx | 46 +-
email-templates/emails/email-verification.tsx | 52 +-
.../emails/login-with-new-device.tsx | 97 +--
email-templates/emails/one-time-access.tsx | 56 +-
email-templates/emails/test.tsx | 21 +-
email-templates/package.json | 16 +-
email-templates/props.ts | 12 +-
pnpm-lock.yaml | 651 +++++++++---------
28 files changed, 788 insertions(+), 627 deletions(-)
create mode 100644 email-templates/components/code-box.tsx
create mode 100644 email-templates/components/details-list.tsx
create mode 100644 email-templates/components/text.tsx
create mode 100644 email-templates/components/theme.ts
diff --git a/backend/internal/email/module.go b/backend/internal/email/module.go
index 1f6b2230..38e318e9 100644
--- a/backend/internal/email/module.go
+++ b/backend/internal/email/module.go
@@ -36,6 +36,7 @@ type template[V any] struct {
type templateData[V any] struct {
AppName string
LogoURL string
+ AppURL string
Data *V
}
@@ -138,6 +139,7 @@ func send[V any](ctx context.Context, module *Module, dbConfig *appconfig.AppCon
templateData := &templateData[V]{
AppName: dbConfig.AppName.String(),
LogoURL: common.EnvConfig.AppURL + "/api/application-images/email",
+ AppURL: common.EnvConfig.AppURL,
Data: data,
}
diff --git a/backend/internal/email/module_test.go b/backend/internal/email/module_test.go
index 88afc793..c067ee80 100644
--- a/backend/internal/email/module_test.go
+++ b/backend/internal/email/module_test.go
@@ -67,7 +67,7 @@ func TestModuleSendsEveryEmailType(t *testing.T) {
{
name: "email verification",
subject: "Verify your Pocket ID Test email address",
- bodyContains: []string{"EMAIL VERIFICATION", "Hello Test User", "https://id.example.test/verify-token"},
+ bodyContains: []string{"VERIFY YOUR EMAIL ADDRESS", "Hello Test User", "https://id.example.test/verify-token"},
send: func(ctx context.Context, config *appconfig.AppConfigModel) error {
return module.SendEmailVerification(ctx, config, user.FullName(), userEmail, "https://id.example.test/verify-token")
},
diff --git a/backend/resources/email-templates/api-key-expiring-soon_html.tmpl b/backend/resources/email-templates/api-key-expiring-soon_html.tmpl
index b9b3bb5c..451840c9 100644
--- a/backend/resources/email-templates/api-key-expiring-soon_html.tmpl
+++ b/backend/resources/email-templates/api-key-expiring-soon_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
API Key Expiring Soon Warning
Hello {{.Data.Name}}, This is a reminder that your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}} .
Please generate a new API key if you need continued access.
{{end}}
\ No newline at end of file
+{{define "root"}}Your API key {{.Data.ApiKeyName}} expires on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}} Your API key {{.Data.ApiKeyName}} expires on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}
API key expiring soon Hello {{.Data.Name}},
Your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}} . Anything that uses this key will stop working once it expires.
To keep access, create a new API key in your {{.AppName}} account settings before then.
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/api-key-expiring-soon_text.tmpl b/backend/resources/email-templates/api-key-expiring-soon_text.tmpl
index 247969d5..fb04995e 100644
--- a/backend/resources/email-templates/api-key-expiring-soon_text.tmpl
+++ b/backend/resources/email-templates/api-key-expiring-soon_text.tmpl
@@ -1,11 +1,7 @@
-{{define "root"}}{{.AppName}}
-
-
-API KEY EXPIRING SOON
-
-Warning
+{{define "root"}}API KEY EXPIRING SOON
Hello {{.Data.Name}},
-This is a reminder that your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}.
-Please generate a new API key if you need continued access.{{end}}
\ No newline at end of file
+Your API key {{.Data.ApiKeyName}} will expire on {{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}. Anything that uses this key will stop working once it expires.
+
+To keep access, create a new API key in your {{.AppName}} account settings before then.{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/email-verification_html.tmpl b/backend/resources/email-templates/email-verification_html.tmpl
index 3d32f78f..bd98dc3f 100644
--- a/backend/resources/email-templates/email-verification_html.tmpl
+++ b/backend/resources/email-templates/email-verification_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
Hello {{.Data.UserFullName}}, Click the button below to verify your email address for {{.AppName}}. This link will expire in 24 hours.
{{end}}
\ No newline at end of file
+{{define "root"}}Confirm the email address for your {{.AppName}} account Confirm the email address for your {{.AppName}} account
Verify your email address Hello {{.Data.UserFullName}},
Click the button below to confirm the email address for your {{.AppName}} account. This link expires in 24 hours.
Or if you don't like clicking buttons, open this link:{{.Data.VerificationLink}}
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/email-verification_text.tmpl b/backend/resources/email-templates/email-verification_text.tmpl
index 660394ed..fdaef652 100644
--- a/backend/resources/email-templates/email-verification_text.tmpl
+++ b/backend/resources/email-templates/email-verification_text.tmpl
@@ -1,10 +1,10 @@
-{{define "root"}}{{.AppName}}
-
-
-EMAIL VERIFICATION
+{{define "root"}}VERIFY YOUR EMAIL ADDRESS
Hello {{.Data.UserFullName}},
-Click the button below to verify your email address for {{.AppName}}. This link will expire in 24 hours.
+Click the button below to confirm the email address for your {{.AppName}} account. This link expires in 24 hours.
-Verify {{.Data.VerificationLink}}{{end}}
\ No newline at end of file
+Verify email address {{.Data.VerificationLink}}
+
+Or if you don't like clicking buttons, open this link:
+{{.Data.VerificationLink}}{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/login-with-new-device_html.tmpl b/backend/resources/email-templates/login-with-new-device_html.tmpl
index a3c5aa5c..06211b1b 100644
--- a/backend/resources/email-templates/login-with-new-device_html.tmpl
+++ b/backend/resources/email-templates/login-with-new-device_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
New Sign-In Detected Warning
Your {{.AppName}} account was recently accessed from a new IP address or browser. If you recognize this activity, no further action is required.
Details Approximate Location
{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
IP Address
{{.Data.IPAddress}}
Device
{{.Data.Device}}
Sign-In Time
{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}
{{end}}
\ No newline at end of file
+{{define "root"}}A new sign-in to your {{.AppName}} account was detected A new sign-in to your {{.AppName}} account was detected
New sign-in detected Your {{.AppName}} account was recently accessed from a new IP address or browser. If this was you, no further action is needed.
Approximate location {{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
IP address {{.Data.IPAddress}}
Time {{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}
If you don't recognize this activity, review the passkeys in your {{.AppName}} account settings and remove any you don't recognize.
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/login-with-new-device_text.tmpl b/backend/resources/email-templates/login-with-new-device_text.tmpl
index 9d8c183c..e858802a 100644
--- a/backend/resources/email-templates/login-with-new-device_text.tmpl
+++ b/backend/resources/email-templates/login-with-new-device_text.tmpl
@@ -1,26 +1,17 @@
-{{define "root"}}{{.AppName}}
+{{define "root"}}NEW SIGN-IN DETECTED
+Your {{.AppName}} account was recently accessed from a new IP address or browser. If this was you, no further action is needed.
-NEW SIGN-IN DETECTED
-
-Warning
-
-Your {{.AppName}} account was recently accessed from a new IP address or browser. If you recognize this activity, no further action is required.
-
-DETAILS
-
-Approximate Location
-
+Approximate location
{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}
-IP Address
-
+IP address
{{.Data.IPAddress}}
Device
-
{{.Data.Device}}
-Sign-In Time
+Time
+{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}
-{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}{{end}}
\ No newline at end of file
+If you don't recognize this activity, review the passkeys in your {{.AppName}} account settings and remove any you don't recognize.{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/one-time-access_html.tmpl b/backend/resources/email-templates/one-time-access_html.tmpl
index 86007b1c..c84b26bd 100644
--- a/backend/resources/email-templates/one-time-access_html.tmpl
+++ b/backend/resources/email-templates/one-time-access_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
Click the button below to sign in to {{.AppName}} with a login code. Or visit {{.Data.LoginLink}} and enter the code {{.Data.Code}} . This code expires in {{.Data.ExpirationString}}.
{{end}}
\ No newline at end of file
+{{define "root"}}Your {{.AppName}} login code is {{.Data.Code}} Your {{.AppName}} login code is {{.Data.Code}}
Your login code Use the code below to sign in to {{.AppName}}. It expires in {{.Data.ExpirationString}}.
Or open {{.Data.LoginLink}} and enter the code manually.
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/one-time-access_text.tmpl b/backend/resources/email-templates/one-time-access_text.tmpl
index 8a311977..040da20a 100644
--- a/backend/resources/email-templates/one-time-access_text.tmpl
+++ b/backend/resources/email-templates/one-time-access_text.tmpl
@@ -1,11 +1,9 @@
-{{define "root"}}{{.AppName}}
+{{define "root"}}YOUR LOGIN CODE
+Use the code below to sign in to {{.AppName}}. It expires in {{.Data.ExpirationString}}.
-YOUR LOGIN CODE
+{{.Data.Code}}
-Click the button below to sign in to {{.AppName}} with a login code.
-Or visit {{.Data.LoginLink}} and enter the code {{.Data.Code}}.
+Sign in {{.Data.LoginLinkWithCode}}
-This code expires in {{.Data.ExpirationString}}.
-
-Sign In {{.Data.LoginLinkWithCode}}{{end}}
\ No newline at end of file
+Or open {{.Data.LoginLink}} and enter the code manually.{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/test_html.tmpl b/backend/resources/email-templates/test_html.tmpl
index ac1b65be..985c5981 100644
--- a/backend/resources/email-templates/test_html.tmpl
+++ b/backend/resources/email-templates/test_html.tmpl
@@ -1 +1 @@
-{{define "root"}}{{.AppName}}
Your email setup is working correctly!
{{end}}
\ No newline at end of file
+{{define "root"}}Your email setup is working correctly Your email setup is working correctly
Test email Your email setup is working correctly!
{{end}}
\ No newline at end of file
diff --git a/backend/resources/email-templates/test_text.tmpl b/backend/resources/email-templates/test_text.tmpl
index 08459a50..9da30fc4 100644
--- a/backend/resources/email-templates/test_text.tmpl
+++ b/backend/resources/email-templates/test_text.tmpl
@@ -1,6 +1,3 @@
-{{define "root"}}{{.AppName}}
-
-
-TEST EMAIL
+{{define "root"}}TEST EMAIL
Your email setup is working correctly!{{end}}
\ No newline at end of file
diff --git a/email-templates/build.ts b/email-templates/build.ts
index 3c235918..3e407a37 100644
--- a/email-templates/build.ts
+++ b/email-templates/build.ts
@@ -1,4 +1,4 @@
-import { render } from "@react-email/components";
+import { render } from "react-email";
import * as fs from "node:fs";
import * as path from "node:path";
@@ -17,9 +17,10 @@ async function buildTemplateFile(
templateName: string,
isPlainText: boolean
) {
- const rendered = await render(Component(Component.TemplateProps), {
- plainText: isPlainText,
- });
+ const rendered = await render(
+ Component(Component.TemplateProps),
+ isPlainText ? { plainText: true } : {},
+ );
// Normalize quotes
const normalized = rendered.replace(/"/g, '"');
diff --git a/email-templates/components/base-template.tsx b/email-templates/components/base-template.tsx
index c4e7590d..5f027b3d 100644
--- a/email-templates/components/base-template.tsx
+++ b/email-templates/components/base-template.tsx
@@ -5,77 +5,115 @@ import {
Head,
Html,
Img,
+ Link,
+ Preview,
Row,
Section,
- Text,
-} from "@react-email/components";
+} from "react-email";
+import type { SharedProps } from "../props";
+import { colors, fonts, radius } from "./theme";
-interface BaseTemplateProps {
- logoURL?: string;
- appName: string;
+interface BaseTemplateProps extends SharedProps {
+ preview?: string;
children: React.ReactNode;
}
export const BaseTemplate = ({
logoURL,
appName,
+ appURL,
+ preview,
children,
-}: BaseTemplateProps) => {
- return (
-
-
-
-
-
-
-
+}: BaseTemplateProps) => (
+
+
+
+
+
+
+ {preview && {preview} }
+
+
+
+
+
+
-
-
- {appName}
-
-
-
- {children}
-
-
-
- );
+
+
+
+
+ {appName}
+
+
+
+
+
+
+
+
+
+
+);
+
+// The heading font is self-hosted by the app, so it loads from the same origin as the logo and works without third-party requests
+// Clients without web font support fall back to the serif stack declared on the headings
+const fontFaceCss = (appURL: string) =>
+ `@font-face{font-family:'Gloock';font-style:normal;font-weight:400;mso-font-alt:'Georgia';src:url(${appURL}/fonts/Gloock-Regular.woff) format('woff');}`;
+
+const bodyStyle = {
+ margin: 0,
+ padding: "32px 16px",
+ backgroundColor: colors.background,
+ fontFamily: fonts.sans,
+ color: colors.text,
};
-const mainStyle = {
- padding: "50px",
- backgroundColor: "#FBFBFB",
- fontFamily: "Arial, sans-serif",
+const containerStyle = {
+ width: "100%",
+ maxWidth: "480px",
+ margin: "0 auto",
};
-const logoStyle = {
- width: "32px",
- height: "32px",
+const headerStyle = {
+ marginBottom: "20px",
+};
+
+const logoColumnStyle = {
+ width: "36px",
verticalAlign: "middle",
};
-const titleStyle = {
- fontSize: "23px",
- fontWeight: "bold",
- margin: "0",
- padding: "0",
+const logoStyle = {
+ display: "block",
+ width: "28px",
+ height: "28px",
+ borderRadius: "6px",
};
-const content = {
- backgroundColor: "white",
- padding: "24px",
- borderRadius: "10px",
- boxShadow: "0 1px 4px 0px rgba(0, 0, 0, 0.1)",
+const appNameStyle = {
+ fontFamily: fonts.serif,
+ fontSize: "20px",
+ lineHeight: "28px",
+ color: colors.foreground,
+ textDecoration: "none",
};
+
+const cardStyle = {
+ backgroundColor: colors.card,
+ border: `1px solid ${colors.border}`,
+ borderRadius: radius.card,
+ padding: "32px",
+ textAlign: "left" as const,
+};
+
diff --git a/email-templates/components/button.tsx b/email-templates/components/button.tsx
index ea1b3eed..037eb942 100644
--- a/email-templates/components/button.tsx
+++ b/email-templates/components/button.tsx
@@ -1,33 +1,33 @@
-import { Button as EmailButton } from "@react-email/components";
+import { Button as EmailButton, Section } from "react-email";
+import { colors, fonts, radius } from "./theme";
interface ButtonProps {
href: string;
children: React.ReactNode;
- style?: React.CSSProperties;
}
-export const Button = ({ href, children, style = {} }: ButtonProps) => {
- const buttonStyle = {
- backgroundColor: "#000000",
- color: "#ffffff",
- padding: "12px 24px",
- borderRadius: "4px",
- fontSize: "15px",
- fontWeight: "500",
- cursor: "pointer",
- marginTop: "10px",
- ...style,
- };
+export const Button = ({ href, children }: ButtonProps) => (
+
+);
- return (
-
-
- {children}
-
-
- );
-};
-
-const buttonContainer = {
+const containerStyle = {
+ margin: "24px 0 8px 0",
textAlign: "center" as const,
};
+
+const buttonStyle = {
+ display: "inline-block",
+ padding: "12px 28px",
+ backgroundColor: colors.primary,
+ color: colors.primaryForeground,
+ borderRadius: radius.pill,
+ fontFamily: fonts.sans,
+ fontSize: "14px",
+ lineHeight: "20px",
+ fontWeight: 500,
+ textDecoration: "none",
+};
diff --git a/email-templates/components/card-header.tsx b/email-templates/components/card-header.tsx
index 6d916218..5a15de86 100644
--- a/email-templates/components/card-header.tsx
+++ b/email-templates/components/card-header.tsx
@@ -1,38 +1,24 @@
-import { Column, Heading, Row, Text } from "@react-email/components";
+import { Heading } from "react-email";
+import { colors, fonts } from "./theme";
-export default function CardHeader({
- title,
- warning,
-}: {
+interface CardHeaderProps {
title: string;
- warning?: boolean;
-}) {
+}
+
+export default function CardHeader({ title }: CardHeaderProps) {
return (
-
-
-
- {title}
-
-
-
- {warning && Warning }
-
-
+
+ {title}
+
);
}
const titleStyle = {
- fontSize: "20px",
- fontWeight: "bold" as const,
- margin: 0,
-};
-
-const warningStyle = {
- backgroundColor: "#ffd966",
- color: "#7f6000",
- padding: "1px 12px",
- borderRadius: "50px",
- fontSize: "12px",
- display: "inline-block",
- margin: 0,
+ margin: "0 0 16px 0",
+ fontFamily: fonts.serif,
+ fontSize: "26px",
+ lineHeight: "32px",
+ fontWeight: 400,
+ letterSpacing: "-0.01em",
+ color: colors.foreground,
};
diff --git a/email-templates/components/code-box.tsx b/email-templates/components/code-box.tsx
new file mode 100644
index 00000000..58c8a347
--- /dev/null
+++ b/email-templates/components/code-box.tsx
@@ -0,0 +1,39 @@
+import { Column, Row, Section, Text } from "react-email";
+import { colors, fonts, radius } from "./theme";
+
+interface CodeBoxProps {
+ code: string;
+}
+
+export const CodeBox = ({ code }: CodeBoxProps) => (
+
+);
+
+const boxStyle = {
+ margin: "8px 0 0 0",
+ backgroundColor: colors.muted,
+ border: `1px solid ${colors.border}`,
+ borderRadius: radius.box,
+};
+
+const cellStyle = {
+ padding: "20px 16px",
+ textAlign: "center" as const,
+};
+
+const codeStyle = {
+ margin: 0,
+ fontFamily: fonts.mono,
+ fontSize: "32px",
+ lineHeight: "40px",
+ fontWeight: 600,
+ letterSpacing: "8px",
+ paddingLeft: "8px",
+ color: colors.foreground,
+};
diff --git a/email-templates/components/details-list.tsx b/email-templates/components/details-list.tsx
new file mode 100644
index 00000000..810062d2
--- /dev/null
+++ b/email-templates/components/details-list.tsx
@@ -0,0 +1,54 @@
+import { Column, Row, Section } from "react-email";
+import { colors, radius } from "./theme";
+
+interface DetailsListProps {
+ items: { label: string; value: string }[];
+}
+
+// A key-value list inside a muted box, used for sign-in details and similar metadata
+// Each value sits below its label so long values never fight the label for width on narrow screens
+// Every item is its own Row so the plain-text build separates entries with a blank line
+export const DetailsList = ({ items }: DetailsListProps) => (
+
+ {items.map((item, index) => {
+ const isLast = index === items.length - 1;
+ return (
+
+
+ {item.label}
+
+ {item.value}
+
+
+ );
+ })}
+
+);
+
+const boxStyle = {
+ margin: "8px 0 0 0",
+ backgroundColor: colors.muted,
+ border: `1px solid ${colors.border}`,
+ borderRadius: radius.box,
+};
+
+const cellStyle = {
+ padding: "12px 16px",
+};
+
+const labelStyle = {
+ fontSize: "12px",
+ lineHeight: "18px",
+ color: colors.mutedForeground,
+};
+
+const valueStyle = {
+ fontSize: "14px",
+ lineHeight: "20px",
+ fontWeight: 500,
+ color: colors.foreground,
+};
+
+const dividerStyle = {
+ borderBottom: `1px solid ${colors.border}`,
+};
diff --git a/email-templates/components/text.tsx b/email-templates/components/text.tsx
new file mode 100644
index 00000000..aa36b192
--- /dev/null
+++ b/email-templates/components/text.tsx
@@ -0,0 +1,29 @@
+import { Text } from "react-email";
+import { colors } from "./theme";
+
+interface TextProps {
+ children: React.ReactNode;
+ style?: React.CSSProperties;
+}
+
+export const Paragraph = ({ children, style }: TextProps) => (
+ {children}
+);
+
+export const Muted = ({ children, style }: TextProps) => (
+ {children}
+);
+
+const paragraphStyle = {
+ margin: "0 0 16px 0",
+ fontSize: "15px",
+ lineHeight: "24px",
+ color: colors.text,
+};
+
+const mutedStyle = {
+ margin: "16px 0 0 0",
+ fontSize: "13px",
+ lineHeight: "20px",
+ color: colors.mutedForeground,
+};
diff --git a/email-templates/components/theme.ts b/email-templates/components/theme.ts
new file mode 100644
index 00000000..f2ae359a
--- /dev/null
+++ b/email-templates/components/theme.ts
@@ -0,0 +1,25 @@
+// Design tokens mirrored from the frontend's app.css (light theme) so emails match the app
+// Email clients have no CSS variable or oklch support, so the values are the resolved hex equivalents
+export const colors = {
+ background: "#f5f5f5",
+ card: "#ffffff",
+ border: "#e5e5e5",
+ muted: "#fafafa",
+ foreground: "#0a0a0a",
+ text: "#404040",
+ mutedForeground: "#737373",
+ primary: "#171717",
+ primaryForeground: "#ffffff",
+};
+
+export const fonts = {
+ sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
+ serif: "Gloock, Georgia, 'Times New Roman', serif",
+ mono: "'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
+};
+
+export const radius = {
+ card: "24px",
+ box: "16px",
+ pill: "9999px",
+};
diff --git a/email-templates/emails/api-key-expiring-soon.tsx b/email-templates/emails/api-key-expiring-soon.tsx
index 2a33987d..7eff7439 100644
--- a/email-templates/emails/api-key-expiring-soon.tsx
+++ b/email-templates/emails/api-key-expiring-soon.tsx
@@ -1,7 +1,11 @@
-import { Text } from "@react-email/components";
import { BaseTemplate } from "../components/base-template";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { Muted, Paragraph } from "../components/text";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface ApiKeyExpiringData {
name: string;
@@ -9,28 +13,30 @@ interface ApiKeyExpiringData {
expiresAt: string;
}
-interface ApiKeyExpiringEmailProps {
- logoURL: string;
- appName: string;
+interface ApiKeyExpiringEmailProps extends SharedProps {
data: ApiKeyExpiringData;
}
export const ApiKeyExpiringEmail = ({
- logoURL,
- appName,
data,
+ ...props
}: ApiKeyExpiringEmailProps) => (
-
-
-
- Hello {data.name},
- This is a reminder that your API key
- {data.apiKeyName}
- {" "}
- will expire on {data.expiresAt} .
-
+
+
+ Hello {data.name},
+
+ Your API key {data.apiKeyName} will expire on{" "}
+ {data.expiresAt} . Anything that uses this key will stop
+ working once it expires.
+
- Please generate a new API key if you need continued access.
+
+ To keep access, create a new API key in your {props.appName} account
+ settings before then.
+
);
@@ -48,8 +54,8 @@ ApiKeyExpiringEmail.TemplateProps = {
ApiKeyExpiringEmail.PreviewProps = {
...sharedPreviewProps,
data: {
- name: "Elias Schneider",
- apiKeyName: "My API Key",
- expiresAt: "September 30, 2024",
+ name: "Elias",
+ apiKeyName: "CI deploy key",
+ expiresAt: "2026-01-30 12:00:00 UTC",
},
};
diff --git a/email-templates/emails/email-verification.tsx b/email-templates/emails/email-verification.tsx
index 11e4d4c2..e5a95c1b 100644
--- a/email-templates/emails/email-verification.tsx
+++ b/email-templates/emails/email-verification.tsx
@@ -1,41 +1,59 @@
-import { Text } from "@react-email/components";
+import { Link } from "react-email";
import { BaseTemplate } from "../components/base-template";
import { Button } from "../components/button";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { Muted, Paragraph } from "../components/text";
+import { colors } from "../components/theme";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface EmailVerificationData {
userFullName: string;
verificationLink: string;
}
-interface EmailVerificationProps {
- logoURL: string;
- appName: string;
+interface EmailVerificationProps extends SharedProps {
data: EmailVerificationData;
}
export const EmailVerification = ({
- logoURL,
- appName,
data,
+ ...props
}: EmailVerificationProps) => (
-
-
+
+
+ Hello {data.userFullName},
+
+ Click the button below to confirm the email address for your{" "}
+ {props.appName} account. This link expires in 24 hours.
+
-
- Hello {data.userFullName},
- Click the button below to verify your email address for {appName}. This
- link will expire in 24 hours.
+ Verify email address
+
+
+ Or if you don't like clicking buttons, open this link:
-
-
- Verify
+
+ {data.verificationLink}
+
+
);
export default EmailVerification;
+const linkStyle = {
+ color: colors.mutedForeground,
+ textDecoration: "underline",
+ wordBreak: "break-all" as const,
+};
+
EmailVerification.TemplateProps = {
...sharedTemplateProps,
data: {
@@ -49,6 +67,6 @@ EmailVerification.PreviewProps = {
data: {
userFullName: "Tim Cook",
verificationLink:
- "https://localhost:1411/user/verify-email?code=abcdefg12345",
+ "https://id.example.com/user/verify-email?code=abcdefg12345",
},
};
diff --git a/email-templates/emails/login-with-new-device.tsx b/email-templates/emails/login-with-new-device.tsx
index fbf008da..93781c19 100644
--- a/email-templates/emails/login-with-new-device.tsx
+++ b/email-templates/emails/login-with-new-device.tsx
@@ -1,7 +1,12 @@
-import { Column, Heading, Row, Text } from "@react-email/components";
import { BaseTemplate } from "../components/base-template";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { DetailsList } from "../components/details-list";
+import { Muted, Paragraph } from "../components/text";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface SignInData {
location: string;
@@ -10,78 +15,44 @@ interface SignInData {
dateTime: string;
}
-interface NewSignInEmailProps {
- logoURL: string;
- appName: string;
+interface NewSignInEmailProps extends SharedProps {
data: SignInData;
}
-export const NewSignInEmail = ({
- logoURL,
- appName,
- data,
-}: NewSignInEmailProps) => (
-
-
-
- Your {appName} account was recently accessed from a new IP address or
- browser. If you recognize this activity, no further action is required.
-
-
- Details
-
+export const NewSignInEmail = ({ data, ...props }: NewSignInEmailProps) => (
+
+
+
+ Your {props.appName} account was recently accessed from a new IP address
+ or browser. If this was you, no further action is needed.
+
-
-
- Approximate Location
- {data.location}
-
-
- IP Address
- {data.ipAddress}
-
-
+
-
-
- Device
- {data.device}
-
-
- Sign-In Time
- {data.dateTime}
-
-
+
+ If you don't recognize this activity, review the passkeys in your{" "}
+ {props.appName} account settings and remove any you don't recognize.
+
);
export default NewSignInEmail;
-const detailsBoxStyle = {
- width: "225px",
-};
-
-const detailsLabelStyle = {
- margin: 0,
- fontSize: "12px",
- color: "gray",
-};
-
-const detailsBoxValueStyle = {
- margin: 0,
-};
-
NewSignInEmail.TemplateProps = {
...sharedTemplateProps,
data: {
- location: "{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}",
+ location:
+ "{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}",
ipAddress: "{{.Data.IPAddress}}",
device: "{{.Data.Device}}",
dateTime: '{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}',
@@ -92,8 +63,8 @@ NewSignInEmail.PreviewProps = {
...sharedPreviewProps,
data: {
location: "San Francisco, USA",
- ipAddress: "127.0.0.1",
+ ipAddress: "203.0.113.42",
device: "Chrome on macOS",
- dateTime: "2024-01-01 12:00 PM UTC",
+ dateTime: "January 2, 2026 at 3:04 PM UTC",
},
};
diff --git a/email-templates/emails/one-time-access.tsx b/email-templates/emails/one-time-access.tsx
index 5abed7c6..cd4fd935 100644
--- a/email-templates/emails/one-time-access.tsx
+++ b/email-templates/emails/one-time-access.tsx
@@ -1,8 +1,15 @@
-import { Link, Text } from "@react-email/components";
+import { Link } from "react-email";
import { BaseTemplate } from "../components/base-template";
import { Button } from "../components/button";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { CodeBox } from "../components/code-box";
+import { Muted, Paragraph } from "../components/text";
+import { colors } from "../components/theme";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
interface OneTimeAccessData {
code: string;
@@ -11,43 +18,44 @@ interface OneTimeAccessData {
expirationString: string;
}
-interface OneTimeAccessEmailProps {
- logoURL: string;
- appName: string;
+interface OneTimeAccessEmailProps extends SharedProps {
data: OneTimeAccessData;
}
export const OneTimeAccessEmail = ({
- logoURL,
- appName,
data,
+ ...props
}: OneTimeAccessEmailProps) => (
-
-
+
+
+
+ Use the code below to sign in to {props.appName}. It expires in{" "}
+ {data.expirationString}.
+
-
- Click the button below to sign in to {appName} with a login code.
-
- Or visit{" "}
+
+
+ Sign in
+
+
+ Or open{" "}
{data.loginLink}
{" "}
- and enter the code {data.code} .
-
-
- This code expires in {data.expirationString}.
-
-
- Sign In
+ and enter the code manually.
+
);
export default OneTimeAccessEmail;
const linkStyle = {
- color: "#000",
+ color: colors.mutedForeground,
textDecoration: "underline",
- fontFamily: "Arial, sans-serif",
+ wordBreak: "break-all" as const,
};
OneTimeAccessEmail.TemplateProps = {
@@ -64,8 +72,8 @@ OneTimeAccessEmail.PreviewProps = {
...sharedPreviewProps,
data: {
code: "123456",
- loginLink: "https://example.com/login",
- buttonCodeLink: "https://example.com/login?code=123456",
+ loginLink: "https://id.example.com/lc",
+ buttonCodeLink: "https://id.example.com/lc/123456",
expirationString: "15 minutes",
},
};
diff --git a/email-templates/emails/test.tsx b/email-templates/emails/test.tsx
index 497c49d6..9dd37cb4 100644
--- a/email-templates/emails/test.tsx
+++ b/email-templates/emails/test.tsx
@@ -1,17 +1,16 @@
-import { Text } from "@react-email/components";
import { BaseTemplate } from "../components/base-template";
import CardHeader from "../components/card-header";
-import { sharedPreviewProps, sharedTemplateProps } from "../props";
+import { Paragraph } from "../components/text";
+import {
+ type SharedProps,
+ sharedPreviewProps,
+ sharedTemplateProps,
+} from "../props";
-interface TestEmailProps {
- logoURL: string;
- appName: string;
-}
-
-export const TestEmail = ({ logoURL, appName }: TestEmailProps) => (
-
-
- Your email setup is working correctly!
+export const TestEmail = (props: SharedProps) => (
+
+
+ Your email setup is working correctly!
);
diff --git a/email-templates/package.json b/email-templates/package.json
index 11e1d514..93f1b18e 100644
--- a/email-templates/package.json
+++ b/email-templates/package.json
@@ -9,16 +9,16 @@
"export": "email export"
},
"dependencies": {
- "@react-email/components": "1.0.12",
- "react": "^19.2.6",
- "react-dom": "^19.2.6"
+ "react": "^19.3.0",
+ "react-dom": "^19.3.0",
+ "react-email": "6.9.5"
},
"devDependencies": {
"@react-email/preview-server": "5.2.10",
- "@types/node": "^25.9.0",
- "@types/react": "^19.2.14",
- "@types/react-dom": "^19.2.3",
- "react-email": "6.9.5",
- "tsx": "^4.22.2"
+ "@react-email/ui": "6.9.5",
+ "@types/node": "^26.6.2",
+ "@types/react": "^19.3.0",
+ "@types/react-dom": "^19.3.0",
+ "tsx": "^4.23.13"
}
}
diff --git a/email-templates/props.ts b/email-templates/props.ts
index 90cc06c5..c4b83a6f 100644
--- a/email-templates/props.ts
+++ b/email-templates/props.ts
@@ -1,9 +1,17 @@
-export const sharedPreviewProps = {
+export interface SharedProps {
+ logoURL: string;
+ appName: string;
+ appURL: string;
+}
+
+export const sharedPreviewProps: SharedProps = {
logoURL: "https://pocket-id.org/img/logo.png",
appName: "Pocket ID",
+ appURL: "https://id.example.com",
};
-export const sharedTemplateProps = {
+export const sharedTemplateProps: SharedProps = {
logoURL: "{{.LogoURL}}",
appName: "{{.AppName}}",
+ appURL: "{{.AppURL}}",
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9b29999b..0254a45d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,33 +17,33 @@ importers:
email-templates:
dependencies:
- '@react-email/components':
- specifier: 1.0.12
- version: 1.0.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
react:
- specifier: ^19.2.6
+ specifier: ^19.3.0
version: 19.3.0
react-dom:
- specifier: ^19.2.6
+ specifier: ^19.3.0
version: 19.3.0(react@19.3.0)
- devDependencies:
- '@react-email/preview-server':
- specifier: 5.2.10
- version: 5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
- '@types/node':
- specifier: ^25.9.0
- version: 25.9.6
- '@types/react':
- specifier: ^19.2.14
- version: 19.3.0
- '@types/react-dom':
- specifier: ^19.2.3
- version: 19.3.0(@types/react@19.3.0)
react-email:
specifier: 6.9.5
version: 6.9.5(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ devDependencies:
+ '@react-email/preview-server':
+ specifier: 5.2.10
+ version: 5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ '@react-email/ui':
+ specifier: 6.9.5
+ version: 6.9.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ '@types/node':
+ specifier: ^26.6.2
+ version: 26.6.2
+ '@types/react':
+ specifier: ^19.3.0
+ version: 19.3.0
+ '@types/react-dom':
+ specifier: ^19.3.0
+ version: 19.3.0(@types/react@19.3.0)
tsx:
- specifier: ^4.22.2
+ specifier: ^4.23.13
version: 4.23.13
frontend:
@@ -309,156 +309,312 @@ packages:
'@emnapi/runtime@1.11.3':
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
+ '@esbuild/aix-ppc64@0.28.1':
+ resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
'@esbuild/aix-ppc64@0.28.2':
resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
+ '@esbuild/android-arm64@0.28.1':
+ resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm64@0.28.2':
resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm@0.28.1':
+ resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-arm@0.28.2':
resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
+ '@esbuild/android-x64@0.28.1':
+ resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/android-x64@0.28.2':
resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
+ '@esbuild/darwin-arm64@0.28.1':
+ resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-arm64@0.28.2':
resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-x64@0.28.1':
+ resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.28.2':
resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
+ '@esbuild/freebsd-arm64@0.28.1':
+ resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-arm64@0.28.2':
resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.28.1':
+ resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.28.2':
resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
+ '@esbuild/linux-arm64@0.28.1':
+ resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm64@0.28.2':
resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm@0.28.1':
+ resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-arm@0.28.2':
resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
+ '@esbuild/linux-ia32@0.28.1':
+ resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-ia32@0.28.2':
resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.28.1':
+ resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.28.2':
resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-mips64el@0.28.1':
+ resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.28.2':
resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-ppc64@0.28.1':
+ resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.28.2':
resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-riscv64@0.28.1':
+ resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.28.2':
resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-s390x@0.28.1':
+ resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-s390x@0.28.2':
resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-x64@0.28.1':
+ resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/linux-x64@0.28.2':
resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
+ '@esbuild/netbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
'@esbuild/netbsd-arm64@0.28.2':
resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.28.1':
+ resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.28.2':
resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
+ '@esbuild/openbsd-arm64@0.28.1':
+ resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
'@esbuild/openbsd-arm64@0.28.2':
resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.28.1':
+ resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.28.2':
resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
+ '@esbuild/openharmony-arm64@0.28.1':
+ resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
'@esbuild/openharmony-arm64@0.28.2':
resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
+ '@esbuild/sunos-x64@0.28.1':
+ resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/sunos-x64@0.28.2':
resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
+ '@esbuild/win32-arm64@0.28.1':
+ resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
'@esbuild/win32-arm64@0.28.2':
resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
+ '@esbuild/win32-ia32@0.28.1':
+ resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
'@esbuild/win32-ia32@0.28.2':
resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-x64@0.28.1':
+ resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@esbuild/win32-x64@0.28.2':
resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
engines: {node: '>=18'}
@@ -942,129 +1098,10 @@ packages:
'@poppinss/macroable@1.1.2':
resolution: {integrity: sha512-FAVBRzzWhYP5mA3lCwLH1A0fKBqq5anyjGet90Z81aRK5c/+LTGUE1zJhZrErjaenBSOOI9BVUs3WVmotneFQA==}
- '@react-email/body@0.3.0':
- resolution: {integrity: sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/button@0.2.1':
- resolution: {integrity: sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/code-block@0.2.1':
- resolution: {integrity: sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/code-inline@0.0.6':
- resolution: {integrity: sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/column@0.0.14':
- resolution: {integrity: sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/components@1.0.12':
- resolution: {integrity: sha512-tH18JhPDWgE+3jnYkzyB6ZrZdfNnEsFe4PwmuXmlOw4NGIysP8wPY5aXZg++pTG9qUabXg1nzX/FGHGkObH8xQ==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/container@0.0.16':
- resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/font@0.0.10':
- resolution: {integrity: sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/head@0.0.13':
- resolution: {integrity: sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/heading@0.0.16':
- resolution: {integrity: sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/hr@0.0.12':
- resolution: {integrity: sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/html@0.0.12':
- resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/img@0.0.12':
- resolution: {integrity: sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/link@0.0.13':
- resolution: {integrity: sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/markdown@0.0.18':
- resolution: {integrity: sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
'@react-email/preview-server@5.2.10':
resolution: {integrity: sha512-cYi21KF+Z/HGXT8RpkQMNFFubBafxyoB9Hn/wrslfDNtdoews2MdsDo6XXKkZvDTRG9SxQN3HGk4v4aoQZc20g==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- '@react-email/preview@0.0.14':
- resolution: {integrity: sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/render@2.0.6':
- resolution: {integrity: sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog==}
- engines: {node: '>=20.0.0'}
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
- react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
-
'@react-email/render@2.1.0':
resolution: {integrity: sha512-F+zE3O6d6sW6Aj2UjvZAA17R7tJKM7kcq2mgV6k4HCT8jeLLFaVP2txMtH1lgqYFRMZ0Gxsd37q2PRyiXLXXxA==}
engines: {node: '>=20.0.0'}
@@ -1072,65 +1109,8 @@ packages:
react: ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
- '@react-email/row@0.0.13':
- resolution: {integrity: sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/section@0.0.17':
- resolution: {integrity: sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
-
- '@react-email/tailwind@2.0.7':
- resolution: {integrity: sha512-kGw80weVFXikcnCXbigTGXGWQ0MRCSYNCudcdkWxebkWYd0FG6/NPoN3V1p/u68/4+NxZwYPVi2fhnp0x23HdA==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- '@react-email/body': '>=0'
- '@react-email/button': '>=0'
- '@react-email/code-block': '>=0'
- '@react-email/code-inline': '>=0'
- '@react-email/container': '>=0'
- '@react-email/heading': '>=0'
- '@react-email/hr': '>=0'
- '@react-email/img': '>=0'
- '@react-email/link': '>=0'
- '@react-email/preview': '>=0'
- '@react-email/text': '>=0'
- react: ^18.0 || ^19.0 || ^19.0.0-rc
- peerDependenciesMeta:
- '@react-email/body':
- optional: true
- '@react-email/button':
- optional: true
- '@react-email/code-block':
- optional: true
- '@react-email/code-inline':
- optional: true
- '@react-email/container':
- optional: true
- '@react-email/heading':
- optional: true
- '@react-email/hr':
- optional: true
- '@react-email/img':
- optional: true
- '@react-email/link':
- optional: true
- '@react-email/preview':
- optional: true
-
- '@react-email/text@0.1.6':
- resolution: {integrity: sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==}
- engines: {node: '>=20.0.0'}
- deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
- peerDependencies:
- react: ^18.0 || ^19.0 || ^19.0.0-rc
+ '@react-email/ui@6.9.5':
+ resolution: {integrity: sha512-UvE0yeSQCvdBm9BoEgvZ5vBi8abk02GbRE+4RQjjtxozy66D/O1GbUgK9RdB26+7EqQdOxdwkGLq5CMCiRO96A==}
'@rolldown/binding-android-arm-eabi@1.2.8':
resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==}
@@ -1545,6 +1525,9 @@ packages:
'@types/node@25.9.6':
resolution: {integrity: sha512-JR6Q/PV5DKFvjrGFVqQJdeG0qvsqQQLDa3TzFrqVwhqRXqwNaxPo2KYCtCQXpOdIulCouKwT7a6in9nFthBAzw==}
+ '@types/node@26.6.2':
+ resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==}
+
'@types/qrcode@1.5.6':
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
@@ -1970,6 +1953,11 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
+ esbuild@0.28.1:
+ resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
esbuild@0.28.2:
resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
engines: {node: '>=18'}
@@ -3223,6 +3211,9 @@ packages:
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
+ undici-types@8.9.0:
+ resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
+
unplugin@2.3.11:
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
engines: {node: '>=18.12.0'}
@@ -3482,81 +3473,159 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@esbuild/aix-ppc64@0.28.1':
+ optional: true
+
'@esbuild/aix-ppc64@0.28.2':
optional: true
+ '@esbuild/android-arm64@0.28.1':
+ optional: true
+
'@esbuild/android-arm64@0.28.2':
optional: true
+ '@esbuild/android-arm@0.28.1':
+ optional: true
+
'@esbuild/android-arm@0.28.2':
optional: true
+ '@esbuild/android-x64@0.28.1':
+ optional: true
+
'@esbuild/android-x64@0.28.2':
optional: true
+ '@esbuild/darwin-arm64@0.28.1':
+ optional: true
+
'@esbuild/darwin-arm64@0.28.2':
optional: true
+ '@esbuild/darwin-x64@0.28.1':
+ optional: true
+
'@esbuild/darwin-x64@0.28.2':
optional: true
+ '@esbuild/freebsd-arm64@0.28.1':
+ optional: true
+
'@esbuild/freebsd-arm64@0.28.2':
optional: true
+ '@esbuild/freebsd-x64@0.28.1':
+ optional: true
+
'@esbuild/freebsd-x64@0.28.2':
optional: true
+ '@esbuild/linux-arm64@0.28.1':
+ optional: true
+
'@esbuild/linux-arm64@0.28.2':
optional: true
+ '@esbuild/linux-arm@0.28.1':
+ optional: true
+
'@esbuild/linux-arm@0.28.2':
optional: true
+ '@esbuild/linux-ia32@0.28.1':
+ optional: true
+
'@esbuild/linux-ia32@0.28.2':
optional: true
+ '@esbuild/linux-loong64@0.28.1':
+ optional: true
+
'@esbuild/linux-loong64@0.28.2':
optional: true
+ '@esbuild/linux-mips64el@0.28.1':
+ optional: true
+
'@esbuild/linux-mips64el@0.28.2':
optional: true
+ '@esbuild/linux-ppc64@0.28.1':
+ optional: true
+
'@esbuild/linux-ppc64@0.28.2':
optional: true
+ '@esbuild/linux-riscv64@0.28.1':
+ optional: true
+
'@esbuild/linux-riscv64@0.28.2':
optional: true
+ '@esbuild/linux-s390x@0.28.1':
+ optional: true
+
'@esbuild/linux-s390x@0.28.2':
optional: true
+ '@esbuild/linux-x64@0.28.1':
+ optional: true
+
'@esbuild/linux-x64@0.28.2':
optional: true
+ '@esbuild/netbsd-arm64@0.28.1':
+ optional: true
+
'@esbuild/netbsd-arm64@0.28.2':
optional: true
+ '@esbuild/netbsd-x64@0.28.1':
+ optional: true
+
'@esbuild/netbsd-x64@0.28.2':
optional: true
+ '@esbuild/openbsd-arm64@0.28.1':
+ optional: true
+
'@esbuild/openbsd-arm64@0.28.2':
optional: true
+ '@esbuild/openbsd-x64@0.28.1':
+ optional: true
+
'@esbuild/openbsd-x64@0.28.2':
optional: true
+ '@esbuild/openharmony-arm64@0.28.1':
+ optional: true
+
'@esbuild/openharmony-arm64@0.28.2':
optional: true
+ '@esbuild/sunos-x64@0.28.1':
+ optional: true
+
'@esbuild/sunos-x64@0.28.2':
optional: true
+ '@esbuild/win32-arm64@0.28.1':
+ optional: true
+
'@esbuild/win32-arm64@0.28.2':
optional: true
+ '@esbuild/win32-ia32@0.28.1':
+ optional: true
+
'@esbuild/win32-ia32@0.28.2':
optional: true
+ '@esbuild/win32-x64@0.28.1':
+ optional: true
+
'@esbuild/win32-x64@0.28.2':
optional: true
@@ -3953,94 +4022,10 @@ snapshots:
'@poppinss/macroable@1.1.2':
optional: true
- '@react-email/body@0.3.0(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/button@0.2.1(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/code-block@0.2.1(react@19.3.0)':
- dependencies:
- prismjs: 1.30.0
- react: 19.3.0
-
- '@react-email/code-inline@0.0.6(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/column@0.0.14(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/components@1.0.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
- dependencies:
- '@react-email/body': 0.3.0(react@19.3.0)
- '@react-email/button': 0.2.1(react@19.3.0)
- '@react-email/code-block': 0.2.1(react@19.3.0)
- '@react-email/code-inline': 0.0.6(react@19.3.0)
- '@react-email/column': 0.0.14(react@19.3.0)
- '@react-email/container': 0.0.16(react@19.3.0)
- '@react-email/font': 0.0.10(react@19.3.0)
- '@react-email/head': 0.0.13(react@19.3.0)
- '@react-email/heading': 0.0.16(react@19.3.0)
- '@react-email/hr': 0.0.12(react@19.3.0)
- '@react-email/html': 0.0.12(react@19.3.0)
- '@react-email/img': 0.0.12(react@19.3.0)
- '@react-email/link': 0.0.13(react@19.3.0)
- '@react-email/markdown': 0.0.18(react@19.3.0)
- '@react-email/preview': 0.0.14(react@19.3.0)
- '@react-email/render': 2.0.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
- '@react-email/row': 0.0.13(react@19.3.0)
- '@react-email/section': 0.0.17(react@19.3.0)
- '@react-email/tailwind': 2.0.7(@react-email/body@0.3.0(react@19.3.0))(@react-email/button@0.2.1(react@19.3.0))(@react-email/code-block@0.2.1(react@19.3.0))(@react-email/code-inline@0.0.6(react@19.3.0))(@react-email/container@0.0.16(react@19.3.0))(@react-email/heading@0.0.16(react@19.3.0))(@react-email/hr@0.0.12(react@19.3.0))(@react-email/img@0.0.12(react@19.3.0))(@react-email/link@0.0.13(react@19.3.0))(@react-email/preview@0.0.14(react@19.3.0))(@react-email/text@0.1.6(react@19.3.0))(react@19.3.0)
- '@react-email/text': 0.1.6(react@19.3.0)
- react: 19.3.0
- transitivePeerDependencies:
- - react-dom
-
- '@react-email/container@0.0.16(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/font@0.0.10(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/head@0.0.13(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/heading@0.0.16(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/hr@0.0.12(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/html@0.0.12(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/img@0.0.12(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/link@0.0.13(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/markdown@0.0.18(react@19.3.0)':
- dependencies:
- marked: 15.0.12
- react: 19.3.0
-
- '@react-email/preview-server@5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
+ '@react-email/preview-server@5.2.10(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
dependencies:
esbuild: 0.28.2
- next: 16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ next: 16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
transitivePeerDependencies:
- '@babel/core'
- '@opentelemetry/api'
@@ -4052,17 +4037,6 @@ snapshots:
- react-dom
- sass
- '@react-email/preview@0.0.14(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/render@2.0.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
- dependencies:
- html-to-text: 9.0.5
- prettier: 3.9.6
- react: 19.3.0
- react-dom: 19.3.0(react@19.3.0)
-
'@react-email/render@2.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
dependencies:
entities: 4.5.0
@@ -4072,34 +4046,20 @@ snapshots:
react: 19.3.0
react-dom: 19.3.0(react@19.3.0)
- '@react-email/row@0.0.13(react@19.3.0)':
+ '@react-email/ui@6.9.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
dependencies:
- react: 19.3.0
-
- '@react-email/section@0.0.17(react@19.3.0)':
- dependencies:
- react: 19.3.0
-
- '@react-email/tailwind@2.0.7(@react-email/body@0.3.0(react@19.3.0))(@react-email/button@0.2.1(react@19.3.0))(@react-email/code-block@0.2.1(react@19.3.0))(@react-email/code-inline@0.0.6(react@19.3.0))(@react-email/container@0.0.16(react@19.3.0))(@react-email/heading@0.0.16(react@19.3.0))(@react-email/hr@0.0.12(react@19.3.0))(@react-email/img@0.0.12(react@19.3.0))(@react-email/link@0.0.13(react@19.3.0))(@react-email/preview@0.0.14(react@19.3.0))(@react-email/text@0.1.6(react@19.3.0))(react@19.3.0)':
- dependencies:
- '@react-email/text': 0.1.6(react@19.3.0)
- react: 19.3.0
- tailwindcss: 4.3.3
- optionalDependencies:
- '@react-email/body': 0.3.0(react@19.3.0)
- '@react-email/button': 0.2.1(react@19.3.0)
- '@react-email/code-block': 0.2.1(react@19.3.0)
- '@react-email/code-inline': 0.0.6(react@19.3.0)
- '@react-email/container': 0.0.16(react@19.3.0)
- '@react-email/heading': 0.0.16(react@19.3.0)
- '@react-email/hr': 0.0.12(react@19.3.0)
- '@react-email/img': 0.0.12(react@19.3.0)
- '@react-email/link': 0.0.13(react@19.3.0)
- '@react-email/preview': 0.0.14(react@19.3.0)
-
- '@react-email/text@0.1.6(react@19.3.0)':
- dependencies:
- react: 19.3.0
+ esbuild: 0.28.1
+ next: 16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ transitivePeerDependencies:
+ - '@babel/core'
+ - '@opentelemetry/api'
+ - '@playwright/test'
+ - '@types/node'
+ - babel-plugin-macros
+ - babel-plugin-react-compiler
+ - react
+ - react-dom
+ - sass
'@rolldown/binding-android-arm-eabi@1.2.8':
optional: true
@@ -4363,7 +4323,7 @@ snapshots:
'@types/cors@2.8.19':
dependencies:
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
'@types/esrecurse@4.3.1': {}
@@ -4375,6 +4335,10 @@ snapshots:
dependencies:
undici-types: 7.24.6
+ '@types/node@26.6.2':
+ dependencies:
+ undici-types: 8.9.0
+
'@types/qrcode@1.5.6':
dependencies:
'@types/node': 25.9.6
@@ -4392,7 +4356,7 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
'@typeschema/class-validator@0.3.0(@types/json-schema@7.0.15)(class-validator@0.14.4)':
dependencies:
@@ -4801,7 +4765,7 @@ snapshots:
engine.io@6.6.10:
dependencies:
'@types/cors': 2.8.19
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
'@types/ws': 8.18.1
accepts: 1.3.8
cookie: 2.0.1
@@ -4838,6 +4802,35 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.4
+ esbuild@0.28.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.1
+ '@esbuild/android-arm': 0.28.1
+ '@esbuild/android-arm64': 0.28.1
+ '@esbuild/android-x64': 0.28.1
+ '@esbuild/darwin-arm64': 0.28.1
+ '@esbuild/darwin-x64': 0.28.1
+ '@esbuild/freebsd-arm64': 0.28.1
+ '@esbuild/freebsd-x64': 0.28.1
+ '@esbuild/linux-arm': 0.28.1
+ '@esbuild/linux-arm64': 0.28.1
+ '@esbuild/linux-ia32': 0.28.1
+ '@esbuild/linux-loong64': 0.28.1
+ '@esbuild/linux-mips64el': 0.28.1
+ '@esbuild/linux-ppc64': 0.28.1
+ '@esbuild/linux-riscv64': 0.28.1
+ '@esbuild/linux-s390x': 0.28.1
+ '@esbuild/linux-x64': 0.28.1
+ '@esbuild/netbsd-arm64': 0.28.1
+ '@esbuild/netbsd-x64': 0.28.1
+ '@esbuild/openbsd-arm64': 0.28.1
+ '@esbuild/openbsd-x64': 0.28.1
+ '@esbuild/openharmony-arm64': 0.28.1
+ '@esbuild/sunos-x64': 0.28.1
+ '@esbuild/win32-arm64': 0.28.1
+ '@esbuild/win32-ia32': 0.28.1
+ '@esbuild/win32-x64': 0.28.1
+
esbuild@0.28.2:
optionalDependencies:
'@esbuild/aix-ppc64': 0.28.2
@@ -5388,7 +5381,7 @@ snapshots:
negotiator@0.6.3: {}
- next@16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.6)(react-dom@19.3.0(react@19.3.0))(react@19.3.0):
+ next@16.3.5(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@26.6.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0):
dependencies:
'@next/env': 16.3.5
'@swc/helpers': 0.5.23
@@ -5409,7 +5402,7 @@ snapshots:
'@next/swc-win32-x64-msvc': 16.3.5
'@opentelemetry/api': 1.9.1
'@playwright/test': 1.63.0
- sharp: 0.35.4(@types/node@25.9.6)
+ sharp: 0.35.4(@types/node@26.6.2)
transitivePeerDependencies:
- '@babel/core'
- '@types/node'
@@ -5752,7 +5745,7 @@ snapshots:
svelte: 5.57.0(@typescript-eslint/types@8.70.0)
tailwind-merge: 3.6.0
- sharp@0.35.4(@types/node@25.9.6):
+ sharp@0.35.4(@types/node@26.6.2):
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
@@ -5783,7 +5776,7 @@ snapshots:
'@img/sharp-win32-arm64': 0.35.4
'@img/sharp-win32-ia32': 0.35.4
'@img/sharp-win32-x64': 0.35.4
- '@types/node': 25.9.6
+ '@types/node': 26.6.2
optional: true
shebang-command@2.0.0:
@@ -6067,6 +6060,8 @@ snapshots:
undici-types@7.24.6: {}
+ undici-types@8.9.0: {}
+
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5