diff --git a/package-lock.json b/package-lock.json
index 9e1ca0f..63c666f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@react-email/components": "^0.0.32",
"@react-email/html": "^0.0.11",
+ "@react-email/render": "^1.0.4",
"@react-email/section": "^0.0.16",
"@react-email/text": "^0.0.11",
"@sentry/nextjs": "^8.35.0",
@@ -3668,23 +3669,6 @@
"react": "^18.0 || ^19.0 || ^19.0.0-rc"
}
},
- "node_modules/@react-email/components/node_modules/@react-email/render": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.4.tgz",
- "integrity": "sha512-8ZXi89d8igBDE6W3zlHBa3GEDWKEUFDAa7i8MvVxnRViQuvsRbibK3ltuPgixxRI5+HgGNCSreBHQKZCkhUdyw==",
- "dependencies": {
- "html-to-text": "9.0.5",
- "prettier": "3.4.2",
- "react-promise-suspense": "0.3.4"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "peerDependencies": {
- "react": "^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
- }
- },
"node_modules/@react-email/container": {
"version": "0.0.15",
"resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.15.tgz",
@@ -3796,12 +3780,12 @@
}
},
"node_modules/@react-email/render": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz",
- "integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.4.tgz",
+ "integrity": "sha512-8ZXi89d8igBDE6W3zlHBa3GEDWKEUFDAa7i8MvVxnRViQuvsRbibK3ltuPgixxRI5+HgGNCSreBHQKZCkhUdyw==",
"dependencies": {
"html-to-text": "9.0.5",
- "js-beautify": "^1.14.11",
+ "prettier": "3.4.2",
"react-promise-suspense": "0.3.4"
},
"engines": {
@@ -5403,11 +5387,11 @@
"dev": true
},
"node_modules/abbrev": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
- "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.0.tgz",
+ "integrity": "sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==",
"engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/acorn": {
@@ -10798,15 +10782,15 @@
}
},
"node_modules/js-beautify": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
- "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.2.tgz",
+ "integrity": "sha512-mcG6CHJxxih+EFAbd5NEBwrosIs6MoJmiNLFYN6kj5SeJMf7n29Ii/H4lt6zGTvmdB9AApuj5cs4zydjuLeqjw==",
"dependencies": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.4",
- "glob": "^10.3.3",
+ "glob": "^11.0.0",
"js-cookie": "^3.0.5",
- "nopt": "^7.2.0"
+ "nopt": "^8.0.0"
},
"bin": {
"css-beautify": "js/bin/css-beautify.js",
@@ -10826,33 +10810,58 @@
}
},
"node_modules/js-beautify/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
+ "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
"dependencies": {
"foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
+ "jackspeak": "^4.0.1",
+ "minimatch": "^10.0.0",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
+ "engines": {
+ "node": "20 || >=22"
+ },
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/js-beautify/node_modules/jackspeak": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
+ "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/js-beautify/node_modules/lru-cache": {
+ "version": "11.0.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
+ "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/js-beautify/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
+ "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -10866,6 +10875,21 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/js-beautify/node_modules/path-scurry": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
+ "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
@@ -11588,17 +11612,17 @@
"license": "MIT"
},
"node_modules/nopt": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
- "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz",
+ "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==",
"dependencies": {
- "abbrev": "^2.0.0"
+ "abbrev": "^3.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ "node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/normalize-path": {
@@ -12871,6 +12895,23 @@
"node": ">=18"
}
},
+ "node_modules/resend/node_modules/@react-email/render": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz",
+ "integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==",
+ "dependencies": {
+ "html-to-text": "9.0.5",
+ "js-beautify": "^1.14.11",
+ "react-promise-suspense": "0.3.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
diff --git a/package.json b/package.json
index c66d300..d47bfeb 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@react-email/components": "^0.0.32",
"@react-email/html": "^0.0.11",
+ "@react-email/render": "^1.0.4",
"@react-email/section": "^0.0.16",
"@react-email/text": "^0.0.11",
"@sentry/nextjs": "^8.35.0",
diff --git a/src/components/email/friend-request-template.tsx b/src/components/email/friend-request-template.tsx
index 4001325..bbcd22f 100644
--- a/src/components/email/friend-request-template.tsx
+++ b/src/components/email/friend-request-template.tsx
@@ -9,6 +9,7 @@ import {
Button,
Hr,
} from "@react-email/components";
+import { render } from "@react-email/render";
import * as React from "react";
interface FriendRequestEmailProps {
@@ -16,7 +17,7 @@ interface FriendRequestEmailProps {
fromUsername: string;
}
-export const FriendRequestEmail = ({
+const FriendRequestEmailTemplate = ({
username,
fromUsername,
}: FriendRequestEmailProps) => {
@@ -115,3 +116,15 @@ const footer = {
fontSize: "14px",
lineHeight: "24px",
};
+
+export const FriendRequestEmail = (props: FriendRequestEmailProps) => {
+ const component = ;
+ const text = render(component, {
+ plainText: true,
+ });
+
+ return {
+ component,
+ text,
+ };
+};
diff --git a/src/components/email/game-complete-template.tsx b/src/components/email/game-complete-template.tsx
index d2504c3..3d9bd61 100644
--- a/src/components/email/game-complete-template.tsx
+++ b/src/components/email/game-complete-template.tsx
@@ -9,6 +9,8 @@ import {
Section,
Heading,
} from "@react-email/components";
+import { render } from "@react-email/render";
+import * as React from "react";
interface GameCompleteEmailProps {
username: string;
@@ -17,7 +19,7 @@ interface GameCompleteEmailProps {
gameId: string;
}
-export const GameCompleteEmail = ({
+const GameCompleteEmailTemplate = ({
username,
winnerUsername,
isWinner,
@@ -47,10 +49,7 @@ export const GameCompleteEmail = ({
Want to see the final scores? Check out the game details:
-
+
View Game Details
@@ -111,3 +110,15 @@ const h1 = {
padding: "0",
textAlign: "center" as const,
};
+
+export const GameCompleteEmail = (props: GameCompleteEmailProps) => {
+ const component = ;
+ const text = render(component, {
+ plainText: true,
+ });
+
+ return {
+ component,
+ text,
+ };
+};
diff --git a/src/components/email/welcome-template.tsx b/src/components/email/welcome-template.tsx
index b1cc415..b63a430 100644
--- a/src/components/email/welcome-template.tsx
+++ b/src/components/email/welcome-template.tsx
@@ -9,14 +9,15 @@ import {
Button,
Hr,
} from "@react-email/components";
+import { render } from "@react-email/render";
import * as React from "react";
interface WelcomeEmailProps {
username: string;
}
-export const WelcomeEmail = ({ username }: WelcomeEmailProps) => {
- const previewText = `Welcome to Blitzer - Let's start scoring!`;
+const WelcomeEmailTemplate = ({ username }: WelcomeEmailProps) => {
+ const previewText = `Welcome to Blitzer - Let's start scoring!`;
return (
@@ -42,8 +43,7 @@ export const WelcomeEmail = ({ username }: WelcomeEmailProps) => {
- If you have any questions, just reply to this email - we're
- always happy to help.
+ This inbox isn't monitored, replies won't be read.
@@ -112,3 +112,15 @@ const footer = {
fontSize: "14px",
lineHeight: "24px",
};
+
+export const WelcomeEmail = (props: WelcomeEmailProps) => {
+ const component = ;
+ const text = render(component, {
+ plainText: true,
+ });
+
+ return {
+ component,
+ text,
+ };
+};
diff --git a/src/server/email.ts b/src/server/email.ts
index c04e089..7ba9f4f 100644
--- a/src/server/email.ts
+++ b/src/server/email.ts
@@ -21,7 +21,8 @@ export async function sendWelcomeEmail(params: {
from: sender,
to: [params.email],
subject: "Welcome to Blitzer!",
- react: WelcomeEmail({ username: params.username }),
+ react: WelcomeEmail({ username: params.username }).component,
+ text: await WelcomeEmail({ username: params.username }).text,
});
if (error) {
@@ -58,7 +59,13 @@ export async function sendGameCompleteEmail(params: {
winnerUsername: params.winnerUsername,
isWinner: params.isWinner,
gameId: params.gameId,
- }),
+ }).component,
+ text: await GameCompleteEmail({
+ username: params.username,
+ winnerUsername: params.winnerUsername,
+ isWinner: params.isWinner,
+ gameId: params.gameId,
+ }).text,
});
if (error) {
@@ -89,7 +96,11 @@ export async function sendFriendRequestEmail(params: {
react: FriendRequestEmail({
username: params.username,
fromUsername: params.fromUsername,
- }),
+ }).component,
+ text: await FriendRequestEmail({
+ username: params.username,
+ fromUsername: params.fromUsername,
+ }).text,
});
if (error) {