Skip to content

Kirimase Init 6. Resend #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: kirimase@0.57.0_init_bun_shadcnui_drizzle_Postgres_Postgre.JS_Lucia_Stripe
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
NEXT_PUBLIC_STRIPE_PRO_PRICE_ID=
NEXT_PUBLIC_STRIPE_MAX_PRICE_ID=
NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID=
NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID=
RESEND_API_KEY=
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion kirimase.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
"packages": [
"drizzle",
"lucia",
"stripe",
"shadcn-ui",
"stripe"
"resend"
],
"preferredPackageManager": "bun",
"t3": false,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"postgres": "^3.4.3",
"react": "^18",
"react-dom": "^18",
"resend": "^3.2.0",
"sonner": "^1.4.3",
"stripe": "^14.21.0",
"tailwind-merge": "^2.2.2",
Expand Down
124 changes: 124 additions & 0 deletions src/app/(app)/resend/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use client";
import Link from "next/link"
import { emailSchema } from "@/lib/email/utils";
import { useRef, useState } from "react";
import { z } from "zod";

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

export default function Home() {
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="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 text-neutral-600 bg-neutral-100 p-0.5">
.env
</span>
</li>
<li>
Update &quot;from:&quot; in{" "}
<span className="ml-1 font-mono font-thin text-neutral-600 bg-neutral-100 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="bg-neutral-50 p-3">{JSON.stringify(errors, null, 2)}</p>
)}
<div>
<label className="text-neutral-700 text-sm">Name</label>
<input
type="text"
placeholder="Tim"
name="name"
ref={nameInputRef}
className={`
w-full px-3 py-2 text-sm rounded-md border focus:outline-neutral-700 ${
!!errors?.name ? "border-red-700" : "border-neutral-200"
}`}
/>
</div>
<div>
<label className="text-muted-foreground">Email</label>
<input
type="email"
placeholder="tim@apple.com"
name="email"
ref={emailInputRef}
className={`
w-full px-3 py-2 text-sm rounded-md border focus:outline-neutral-700 ${
!!errors?.email ? "border-red-700" : "border-neutral-200"
}`}
/>
</div>
<button
onClick={() => sendEmail()}
className="text-sm bg-black text-white px-4 py-2.5 rounded-lg hover:bg-gray-800 disabled:opacity-70"
disabled={sending}
>
{sending ? "sending..." : "Send Email"}
</button>
</form>
</main>
);
}

22 changes: 22 additions & 0 deletions 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/index";
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: "Kirimase <onboarding@resend.dev>",
to: [email],
subject: "Hello world!",
react: EmailTemplate({ firstName: name }),
text: "Email powered by Resend.",
});

return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error });
}
}
27 changes: 27 additions & 0 deletions 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 Kirimase 😊</p>
</div>
);
4 changes: 4 additions & 0 deletions 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 "@/lib/env.mjs";

export const resend = new Resend(env.RESEND_API_KEY);
6 changes: 6 additions & 0 deletions 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(),
});
5 changes: 3 additions & 2 deletions src/lib/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export const env = createEnv({

STRIPE_SECRET_KEY: z.string().min(1),
STRIPE_WEBHOOK_SECRET: z.string().min(1),
RESEND_API_KEY: z.string().min(1),
},
client: {
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1),
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1),
NEXT_PUBLIC_STRIPE_PRO_PRICE_ID: z.string().min(1),
NEXT_PUBLIC_STRIPE_MAX_PRICE_ID: z.string().min(1),
NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID: z.string().min(1), // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1),
Expand All @@ -24,7 +25,7 @@ export const env = createEnv({
// },
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
NEXT_PUBLIC_STRIPE_PRO_PRICE_ID: process.env.NEXT_PUBLIC_STRIPE_PRO_PRICE_ID,
NEXT_PUBLIC_STRIPE_MAX_PRICE_ID: process.env.NEXT_PUBLIC_STRIPE_MAX_PRICE_ID,
NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID: process.env.NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID, // NEXT_PUBLIC_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_PUBLISHABLE_KEY,
Expand Down