Skip to content

Commit af1ad03

Browse files
committed
feat: verify email
1 parent e674441 commit af1ad03

File tree

4 files changed

+165
-68
lines changed

4 files changed

+165
-68
lines changed

apps/web/emails/index.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { ReactElement } from "react"
2+
3+
import { render } from "@react-email/components"
4+
import nodemailer from "nodemailer"
5+
import { CreateEmailOptions } from "resend"
6+
7+
import { resend } from "@/lib/resend"
8+
9+
// Send email using SMTP (Recommended for local development)
10+
const sendEmailViaSMTP = async ({
11+
email,
12+
subject,
13+
text,
14+
react,
15+
}: Pick<CreateEmailOptions, "subject" | "text" | "react"> & {
16+
email: string
17+
}) => {
18+
const transporter = nodemailer.createTransport({
19+
host: process.env.SMTP_HOST,
20+
port: process.env.SMTP_PORT,
21+
auth: {
22+
user: process.env.SMTP_USER,
23+
pass: process.env.SMTP_PASSWORD,
24+
},
25+
secure: false,
26+
tls: {
27+
rejectUnauthorized: false,
28+
},
29+
})
30+
31+
const info = await transporter.sendMail({
32+
from: "noreply@example.com",
33+
to: email,
34+
subject,
35+
text,
36+
html: render(react as ReactElement),
37+
})
38+
39+
console.info("Email sent: %s", info.messageId)
40+
}
41+
42+
export const sendEmailViaResend = async ({
43+
email,
44+
subject,
45+
from,
46+
bcc,
47+
replyToFromEmail,
48+
text,
49+
react,
50+
scheduledAt,
51+
marketing,
52+
}: Omit<CreateEmailOptions, "to" | "from"> & {
53+
email: string
54+
from?: string
55+
replyToFromEmail?: boolean
56+
marketing?: boolean
57+
}) => {
58+
if (!resend) {
59+
console.info("RESEND_API_KEY is not set in the .env. Skipping sending email.")
60+
return
61+
}
62+
63+
return await resend.emails.send({
64+
to: email,
65+
from:
66+
from || (marketing ? "Steven from Dub.co <steven@ship.dub.co>" : "Dub.co <system@dub.co>"),
67+
bcc: bcc,
68+
...(!replyToFromEmail && {
69+
replyTo: "support@dub.co",
70+
}),
71+
subject: subject,
72+
text: text,
73+
react: react,
74+
scheduledAt,
75+
...(marketing && {
76+
headers: {
77+
"List-Unsubscribe": "https://app.dub.co/account/settings",
78+
},
79+
}),
80+
})
81+
}
82+
83+
export const sendEmail = async ({
84+
email,
85+
subject,
86+
from,
87+
bcc,
88+
replyToFromEmail,
89+
text,
90+
react,
91+
scheduledAt,
92+
marketing,
93+
}: Omit<CreateEmailOptions, "to" | "from"> & {
94+
email: string
95+
from?: string
96+
replyToFromEmail?: boolean
97+
marketing?: boolean
98+
}) => {
99+
if (resend) {
100+
return await sendEmailViaResend({
101+
email,
102+
subject,
103+
from,
104+
bcc,
105+
replyToFromEmail,
106+
text,
107+
react,
108+
scheduledAt,
109+
marketing,
110+
})
111+
}
112+
113+
// Fallback to SMTP if Resend is not configured
114+
const smtpConfigured = Boolean(process.env.SMTP_HOST && process.env.SMTP_PORT)
115+
116+
if (smtpConfigured) {
117+
return await sendEmailViaSMTP({
118+
email,
119+
subject,
120+
text,
121+
react,
122+
})
123+
}
124+
125+
console.info(
126+
"Email sending failed: Neither SMTP nor Resend is configured. Please set up at least one email service to send emails."
127+
)
128+
}

apps/web/emails/verify-email.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import {
22
Body,
3+
Button,
34
Container,
45
Head,
56
Heading,
67
Html,
7-
Img,
8+
Link,
89
Preview,
910
Section,
1011
Tailwind,
@@ -19,26 +20,24 @@ export default function VerifyEmail({}) {
1920
<Tailwind>
2021
<Body className="mx-auto my-auto bg-white font-sans">
2122
<Container className="mx-auto my-10 max-w-[500px] rounded border border-solid border-gray-200 px-10 py-5">
22-
<Section className="mt-8">
23-
{/* <Img
24-
src={DUB_WORDMARK}
25-
height="40"
26-
alt="Dub"
27-
className="mx-auto my-0"
28-
/> */}
29-
</Section>
23+
<Section className="mt-8 text-center text-[24px] font-bold">Next Forum</Section>
3024
<Heading className="mx-0 my-7 p-0 text-center text-xl font-semibold text-black">
3125
Please confirm your email address
3226
</Heading>
3327
<Text className="mx-auto text-sm leading-6">
3428
Click link below to verify your email:
3529
</Text>
3630
<Section className="my-8">
37-
<div className="mx-auto w-fit rounded-xl px-6 py-3 text-center font-mono text-2xl font-semibold tracking-[0.25em]">
38-
123456
39-
</div>
31+
<Link
32+
href=""
33+
className="align-center flex w-[100%] justify-center rounded-sm bg-[tomato] py-2 text-center font-medium text-white"
34+
>
35+
GET STARTED
36+
</Link>
4037
</Section>
41-
<Text className="text-sm leading-6 text-black">This code expires in 10 minutes.</Text>
38+
<Text className="text-sm leading-6 text-black">
39+
This link is expired in 10 minutes.
40+
</Text>
4241
</Container>
4342
</Body>
4443
</Tailwind>

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"next-auth": "5.0.0-beta.20",
5252
"next-intl": "^3.10.0",
5353
"next-themes": "^0.2.1",
54+
"nodemailer": "^6.9.15",
5455
"novel": "^0.5.0",
5556
"postcss": "^8.4.28",
5657
"prismjs": "^1.29.0",

pnpm-lock.yaml

Lines changed: 24 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)