Skip to content

Commit

Permalink
added email sending feature to template
Browse files Browse the repository at this point in the history
  • Loading branch information
shreyashrangrej committed Sep 28, 2023
1 parent 4131441 commit 1a366a5
Show file tree
Hide file tree
Showing 16 changed files with 344 additions and 2 deletions.
1 change: 1 addition & 0 deletions entapex-live/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.4",
"resend": "^1.1.0",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6",
Expand Down
108 changes: 108 additions & 0 deletions entapex-live/src/app/(root)/email/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"use client";
import Link from "next/link";
import { emailSchema } from "@/lib/email/utils";
import { useRef, useState } from "react";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

type FormInput = z.infer<typeof emailSchema>;
type Errors = { [K in keyof FormInput]: string[] };

export default function Page() {
const [sending, setSending] = useState(false);
const [errors, setErrors] = useState<Errors | null>(null);
const nameInputRef = useRef<HTMLInputElement>(null);
const emailInputRef = useRef<HTMLInputElement>(null);

const sendEmail = async () => {
setSending(true);
setErrors(null);
try {
const payload = emailSchema.parse({
name: nameInputRef.current?.value,
email: emailInputRef.current?.value,
});
console.log(payload);
const req = await fetch("/api/email", {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
});
const { id } = await req.json();
if (id) alert("Successfully sent!");
} catch (err) {
if (err instanceof z.ZodError) {
setErrors(err.flatten().fieldErrors as Errors);
}
} finally {
setSending(false);
}
};
return (
<main className="max-w-2xl mx-auto p-4 md:p-0">
<div>
<h1 className="text-2xl font-bold my-4">Send Email with Resend</h1>
<div>
<ol className="list-decimal list-inside space-y-1">
<li>
<Link
className="text-primary hover:text-muted-foreground underline"
href="https://resend.com/signup"
>
Sign up
</Link>{" "}
or{" "}
<Link
className="text-primary hover:text-muted-foreground underline"
href="https://resend.com/login"
>
Login
</Link>{" "}
to your Resend account
</li>
<li>Add and verify your domain</li>
<li>
Create an API Key and add to{" "}
<span className="ml-1 font-mono font-thin bg-zinc-100 dark:bg-slate-800 p-0.5">
.env
</span>
</li>
<li>
Update &quot;from:&quot; in{" "}
<span className="ml-1 font-mono font-thin bg-zinc-100 dark:bg-slate-800 p-0.5">
app/api/email/route.ts
</span>
</li>
<li>Send email 🎉</li>
</ol>
</div>
</div>
<form
onSubmit={(e) => e.preventDefault()}
className="space-y-3 pt-4 border-t mt-4"
>
{errors && <p className="p-3">{JSON.stringify(errors, null, 2)}</p>}
<div>
<Label className="text-muted-foreground">Name</Label>
<Input type="text" placeholder="Tim" name="name" ref={nameInputRef} />
</div>
<div>
<Label className="text-muted-foreground">Email</Label>
<Input
type="email"
placeholder="tim@apple.com"
name="email"
ref={emailInputRef}
/>
</div>
<Button onClick={() => sendEmail()} disabled={sending}>
{sending ? "sending..." : "Send Email"}
</Button>
</form>
</main>
);
}
22 changes: 22 additions & 0 deletions entapex-live/src/app/api/email/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { EmailTemplate } from "@/components/emails/FirstEmail";
import { resend } from "@/lib/email";
import { emailSchema } from "@/lib/email/utils";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const body = await request.json();
const { name, email } = emailSchema.parse(body);
try {
const data = await resend.emails.send({
from: "EntApex App <noreply@entapex.com>",
to: [email],
subject: "Hello world!",
react: EmailTemplate({ firstName: name }),
text: "Email powered by Resend.",
});

return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error });
}
}
3 changes: 3 additions & 0 deletions entapex-live/src/components/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const Profile = ({ user }: UserAccountProps) => {
<Link href="/protected">
<Button>Protected</Button>
</Link>
<Link href="/email">
<Button>Email</Button>
</Link>
</div>
</>
);
Expand Down
27 changes: 27 additions & 0 deletions entapex-live/src/components/emails/FirstEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";

interface EmailTemplateProps {
firstName: string;
}

export const EmailTemplate: React.FC<Readonly<EmailTemplateProps>> = ({
firstName,
}) => (
<div>
<h1>Welcome, {firstName}!</h1>
<p>
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim
labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet.
Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum
Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident.
Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex
occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat
officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in
Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non
excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco
ut ea consectetur et est culpa et culpa duis.
</p>
<hr />
<p>Sent with help from Resend and EntApex App 😊</p>
</div>
);
4 changes: 4 additions & 0 deletions entapex-live/src/lib/email/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Resend } from "resend";
import { env } from "@/env.mjs";

export const resend = new Resend(env.RESEND_API);
6 changes: 6 additions & 0 deletions entapex-live/src/lib/email/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from "zod";

export const emailSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
});
2 changes: 1 addition & 1 deletion entapex-live/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ export default withAuth(
);

export const config = {
matcher: ["/protected/:path*", "/login", "/register"],
matcher: ["/protected/:path*", "/login", "/register", "/email/:path"],
};
1 change: 1 addition & 0 deletions templates/entapex-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.4",
"resend": "^1.1.0",
"tailwind-merge": "^1.14.0",
"tailwindcss": "3.3.3",
"tailwindcss-animate": "^1.0.6",
Expand Down
108 changes: 108 additions & 0 deletions templates/entapex-app/src/app/(root)/email/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"use client";
import Link from "next/link";
import { emailSchema } from "@/lib/email/utils";
import { useRef, useState } from "react";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

type FormInput = z.infer<typeof emailSchema>;
type Errors = { [K in keyof FormInput]: string[] };

export default function Page() {
const [sending, setSending] = useState(false);
const [errors, setErrors] = useState<Errors | null>(null);
const nameInputRef = useRef<HTMLInputElement>(null);
const emailInputRef = useRef<HTMLInputElement>(null);

const sendEmail = async () => {
setSending(true);
setErrors(null);
try {
const payload = emailSchema.parse({
name: nameInputRef.current?.value,
email: emailInputRef.current?.value,
});
console.log(payload);
const req = await fetch("/api/email", {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
});
const { id } = await req.json();
if (id) alert("Successfully sent!");
} catch (err) {
if (err instanceof z.ZodError) {
setErrors(err.flatten().fieldErrors as Errors);
}
} finally {
setSending(false);
}
};
return (
<main className="max-w-2xl mx-auto p-4 md:p-0">
<div>
<h1 className="text-2xl font-bold my-4">Send Email with Resend</h1>
<div>
<ol className="list-decimal list-inside space-y-1">
<li>
<Link
className="text-primary hover:text-muted-foreground underline"
href="https://resend.com/signup"
>
Sign up
</Link>{" "}
or{" "}
<Link
className="text-primary hover:text-muted-foreground underline"
href="https://resend.com/login"
>
Login
</Link>{" "}
to your Resend account
</li>
<li>Add and verify your domain</li>
<li>
Create an API Key and add to{" "}
<span className="ml-1 font-mono font-thin bg-zinc-100 dark:bg-slate-800 p-0.5">
.env
</span>
</li>
<li>
Update &quot;from:&quot; in{" "}
<span className="ml-1 font-mono font-thin bg-zinc-100 dark:bg-slate-800 p-0.5">
app/api/email/route.ts
</span>
</li>
<li>Send email 🎉</li>
</ol>
</div>
</div>
<form
onSubmit={(e) => e.preventDefault()}
className="space-y-3 pt-4 border-t mt-4"
>
{errors && <p className="p-3">{JSON.stringify(errors, null, 2)}</p>}
<div>
<Label className="text-muted-foreground">Name</Label>
<Input type="text" placeholder="Tim" name="name" ref={nameInputRef} />
</div>
<div>
<Label className="text-muted-foreground">Email</Label>
<Input
type="email"
placeholder="tim@apple.com"
name="email"
ref={emailInputRef}
/>
</div>
<Button onClick={() => sendEmail()} disabled={sending}>
{sending ? "sending..." : "Send Email"}
</Button>
</form>
</main>
);
}
22 changes: 22 additions & 0 deletions templates/entapex-app/src/app/api/email/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { EmailTemplate } from "@/components/emails/FirstEmail";
import { resend } from "@/lib/email";
import { emailSchema } from "@/lib/email/utils";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
const body = await request.json();
const { name, email } = emailSchema.parse(body);
try {
const data = await resend.emails.send({
from: "EntApex App <noreply@entapex.com>",
to: [email],
subject: "Hello world!",
react: EmailTemplate({ firstName: name }),
text: "Email powered by Resend.",
});

return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error });
}
}
3 changes: 3 additions & 0 deletions templates/entapex-app/src/components/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const Profile = ({ user }: UserAccountProps) => {
<Link href="/protected">
<Button>Protected</Button>
</Link>
<Link href="/email">
<Button>Email</Button>
</Link>
</div>
</>
);
Expand Down
27 changes: 27 additions & 0 deletions templates/entapex-app/src/components/emails/FirstEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";

interface EmailTemplateProps {
firstName: string;
}

export const EmailTemplate: React.FC<Readonly<EmailTemplateProps>> = ({
firstName,
}) => (
<div>
<h1>Welcome, {firstName}!</h1>
<p>
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim
labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet.
Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum
Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident.
Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex
occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat
officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in
Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non
excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco
ut ea consectetur et est culpa et culpa duis.
</p>
<hr />
<p>Sent with help from Resend and EntApex App 😊</p>
</div>
);
4 changes: 4 additions & 0 deletions templates/entapex-app/src/lib/email/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Resend } from "resend";
import { env } from "@/env.mjs";

export const resend = new Resend(env.RESEND_API);
6 changes: 6 additions & 0 deletions templates/entapex-app/src/lib/email/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from "zod";

export const emailSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
});
2 changes: 1 addition & 1 deletion templates/entapex-app/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ export default withAuth(
);

export const config = {
matcher: ["/protected/:path*", "/login", "/register"],
matcher: ["/protected/:path*", "/login", "/register", "/email/:path"],
};

0 comments on commit 1a366a5

Please sign in to comment.