feat: add React Email templates and Resend email configuration

- Integrate React Email library for creating email templates
- Add email templates for newsletter welcome, reset password, and email verification
- Create email format styles for consistent email design
- Configure Resend email service with new mail utility
- Update authentication flow to use React Email templates
- Add new email-related dependencies in package.json
This commit is contained in:
javayhu 2025-03-07 19:14:13 +08:00
parent 801ece4bdd
commit 8aaead0245
8 changed files with 1681 additions and 25 deletions

61
emails/email-formats.ts Normal file
View File

@ -0,0 +1,61 @@
/**
* https://demo.react.email/preview/welcome/stripe-welcome
*/
export const main = {
fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
export const container = {
};
export const box = {
padding: "8px",
};
export const hr = {
borderColor: "#e6ebf1",
margin: "20px 0",
};
export const paragraph = {
color: "#525f7f",
fontSize: "16px",
lineHeight: "24px",
textAlign: "left" as const,
};
export const anchor = {
color: "#556cd6",
};
export const button = {
backgroundColor: "#656ee8",
borderRadius: "5px",
color: "#fff",
fontSize: "16px",
fontWeight: "bold",
textDecoration: "none",
textAlign: "center" as const,
display: "block",
width: "100%",
padding: "10px",
};
export const footer = {
width: "100%",
color: "#8898aa",
fontSize: "12px",
lineHeight: "16px",
display: "table",
tableLayout: "fixed" as const,
};
export const footerLeft = {
display: "table-cell",
textAlign: "left" as const,
};
export const footerRight = {
display: "table-cell",
textAlign: "right" as const,
};

View File

@ -0,0 +1,91 @@
import { siteConfig } from "@/config/site";
import {
Body,
Container,
Head,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
} from "@react-email/components";
import {
anchor,
box,
container,
footer,
footerLeft,
footerRight,
hr,
main,
paragraph,
} from "./email-formats";
import { getBaseUrl } from "@/lib/urls/get-base-url";
/**
* email for newsletter welcome
*/
export const NewsletterWelcomeEmail = ({ email }: { email: string }) => {
const unsubscribeUrl = `${getBaseUrl()}/unsubscribe?email=${encodeURIComponent(email)}`;
return (
<Html>
<Head />
<Preview>Welcome to {siteConfig.name}!</Preview>
<Body style={main}>
<Container style={container}>
<Section style={box}>
<Img
src={`${getBaseUrl()}/logo.png`}
width="32"
height="32"
alt="Logo"
/>
<Hr style={hr} />
<Text style={paragraph}>
Welcome to our community!
</Text>
<Text style={paragraph}>
We value your participation and feedback. Please don't hesitate to
reach out to us if you have any questions or suggestions.
</Text>
<Text style={paragraph}>
Thanks, <br />
The{" "}
<Link style={anchor} href={getBaseUrl()}>
{siteConfig.name}
</Link>{" "}
team
</Text>
<Hr style={hr} />
<Text style={footer}>
<span style={footerLeft}>
&copy; {new Date().getFullYear()}
&nbsp; All Rights Reserved.
</span>
<span style={footerRight}>
</span>
</Text>
<Text style={footer}>
<span>
If you wish to unsubscribe, please{" "}
<Link style={anchor} href={unsubscribeUrl} target="_blank">
click here
</Link>
.
</span>
</Text>
</Section>
</Container>
</Body>
</Html>
);
};
NewsletterWelcomeEmail.PreviewProps = {
email: "support@mksaas.com",
};
export default NewsletterWelcomeEmail;

101
emails/reset-password.tsx Normal file
View File

@ -0,0 +1,101 @@
import { siteConfig } from "@/config/site";
import {
Body,
Button,
Container,
Head,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
} from "@react-email/components";
import {
anchor,
box,
button,
container,
footer,
footerLeft,
footerRight,
hr,
main,
paragraph,
} from "./email-formats";
import { getBaseUrl } from "@/lib/urls/get-base-url";
interface ResetPasswordEmailProps {
userName?: string;
resetLink?: string;
}
/**
* email for reset password
*/
export const ResetPasswordEmail = ({
userName,
resetLink,
}: ResetPasswordEmailProps) => {
return (
<Html>
<Head />
<Preview>Reset your password</Preview>
<Body style={main}>
<Container style={container}>
<Section style={box}>
<Img
src={`${getBaseUrl()}/logo.png`}
width="32"
height="32"
alt="Logo"
/>
<Hr style={hr} />
<Text style={paragraph}>Hi {userName},</Text>
<Text style={paragraph}>
Someone recently requested a password change for your account. If
this was you, you can set a new password here:
</Text>
<Button style={button} href={resetLink}>
Reset password
</Button>
<Hr style={hr} />
<Text style={paragraph}>
If you don&apos;t want to change your password or didn&apos;t
request this, just ignore and delete this message.
</Text>
<Text style={paragraph}>
To keep your account secure, please don&apos;t forward this email
to anyone.
</Text>
<Text style={paragraph}>
Thanks, <br />
The{" "}
<Link style={anchor} href={getBaseUrl()}>
{siteConfig.name}
</Link>{" "}
team
</Text>
<Hr style={hr} />
<Text style={footer}>
<span style={footerLeft}>
&copy; {new Date().getFullYear()}
&nbsp;&nbsp; All Rights Reserved.
</span>
<span style={footerRight}>
</span>
</Text>
</Section>
</Container>
</Body>
</Html>
);
};
ResetPasswordEmail.PreviewProps = {
userName: "Mksaas",
resetLink: "https://demo.mksaas.com",
} as ResetPasswordEmailProps;
export default ResetPasswordEmail;

93
emails/verify-email.tsx Normal file
View File

@ -0,0 +1,93 @@
import { siteConfig } from "@/config/site";
import {
Body,
Button,
Container,
Head,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
} from "@react-email/components";
import {
anchor,
box,
button,
container,
footer,
footerLeft,
footerRight,
hr,
main,
paragraph,
} from "./email-formats";
import { getBaseUrl } from "@/lib/urls/get-base-url";
interface VerifyEmailProps {
confirmLink?: string;
}
/**
* email for verify email
*/
export const VerifyEmail = ({ confirmLink }: VerifyEmailProps) => {
return (
<Html>
<Head />
<Preview>Confirm your email address</Preview>
<Body style={main}>
<Container style={container}>
<Section style={box}>
<Img
src={`${getBaseUrl()}/logo.png`}
width="32"
height="32"
alt="Logo"
/>
<Hr style={hr} />
<Text style={paragraph}>Confirm your email address</Text>
<Text style={paragraph}>
Thanks for starting the new account creation process. We want to
make sure it's really you. Please click the confirmation link to
continue.
</Text>
<Button style={button} href={confirmLink}>
Confirm Email
</Button>
<Hr style={hr} />
<Text style={paragraph}>
If you don&apos;t want to create an account, you can ignore this
message.
</Text>
<Text style={paragraph}>
Thanks, <br />
The{" "}
<Link style={anchor} href={getBaseUrl()}>
{siteConfig.name}
</Link>{" "}
team
</Text>
<Hr style={hr} />
<Text style={footer}>
<span style={footerLeft}>
&copy; {new Date().getFullYear()}
&nbsp;&nbsp; All Rights Reserved.
</span>
<span style={footerRight}>
</span>
</Text>
</Section>
</Container>
</Body>
</Html>
);
};
VerifyEmail.PreviewProps = {
confirmLink: "https://demo.mksaas.com",
} as VerifyEmailProps;
export default VerifyEmail;

View File

@ -6,7 +6,8 @@
"dev": "concurrently \"content-collections watch\" \"next dev\"",
"build": "content-collections build && next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"email": "email dev --dir emails --port 3333"
},
"dependencies": {
"@ai-sdk/openai": "^1.1.13",
@ -29,6 +30,7 @@
"@radix-ui/react-toggle": "^1.1.2",
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@react-email/components": "0.0.33",
"@stripe/stripe-js": "^5.6.0",
"@types/canvas-confetti": "^1.9.0",
"ai": "^4.1.45",
@ -84,6 +86,7 @@
"concurrently": "^9.1.2",
"drizzle-kit": "^0.30.4",
"postcss": "^8",
"react-email": "3.0.7",
"tailwindcss": "^3.4.1",
"tsx": "^4.19.3",
"typescript": "^5"

1345
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,11 @@ import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { user, session, account, verification } from "@/db/schema";
import { siteConfig } from "@/config/site";
import { resend } from "@/lib/email/resend";
import { resend } from "@/lib/mail";
import { admin } from "better-auth/plugins";
import db from "@/db/index";
import ResetPasswordEmail from "../../emails/reset-password";
import VerifyEmail from "../../emails/verify-email";
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
@ -44,7 +46,7 @@ export const auth = betterAuth({
from,
to: user.email,
subject: "Reset your password",
react: `Click the link to reset your password: ${url}`,
react: ResetPasswordEmail({ userName: user.name, resetLink: url }),
});
},
},
@ -56,8 +58,8 @@ export const auth = betterAuth({
await resend.emails.send({
from,
to: user.email,
subject: "Verify your email address",
text: `Click the link to verify your email: ${url}`,
subject: "Confirm your email address",
react: VerifyEmail({ confirmLink: url }),
});
},
},