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:
parent
801ece4bdd
commit
8aaead0245
61
emails/email-formats.ts
Normal file
61
emails/email-formats.ts
Normal 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,
|
||||
};
|
91
emails/newsletter-welcome.tsx
Normal file
91
emails/newsletter-welcome.tsx
Normal 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}>
|
||||
© {new Date().getFullYear()}
|
||||
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
101
emails/reset-password.tsx
Normal 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't want to change your password or didn't
|
||||
request this, just ignore and delete this message.
|
||||
</Text>
|
||||
<Text style={paragraph}>
|
||||
To keep your account secure, please don'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}>
|
||||
© {new Date().getFullYear()}
|
||||
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
93
emails/verify-email.tsx
Normal 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'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}>
|
||||
© {new Date().getFullYear()}
|
||||
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;
|
@ -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
1345
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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 }),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user