diff --git a/.env b/.env index f734baa..7d05487 100644 --- a/.env +++ b/.env @@ -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= \ No newline at end of file +NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID= +RESEND_API_KEY= \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index f8c077a..6dbc53a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/kirimase.config.json b/kirimase.config.json index 9a70cf2..eed193e 100644 --- a/kirimase.config.json +++ b/kirimase.config.json @@ -3,8 +3,9 @@ "packages": [ "drizzle", "lucia", + "stripe", "shadcn-ui", - "stripe" + "resend" ], "preferredPackageManager": "bun", "t3": false, diff --git a/package.json b/package.json index de03151..1d5e181 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/(app)/resend/page.tsx b/src/app/(app)/resend/page.tsx new file mode 100644 index 0000000..f59d358 --- /dev/null +++ b/src/app/(app)/resend/page.tsx @@ -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; +type Errors = { [K in keyof FormInput]: string[] }; + +export default function Home() { + const [sending, setSending] = useState(false); + const [errors, setErrors] = useState(null); + const nameInputRef = useRef(null); + const emailInputRef = useRef(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 ( +
+
+

Send Email with Resend

+
+
    +
  1. + + Sign up + {" "} + or{" "} + + Login + {" "} + to your Resend account +
  2. +
  3. Add and verify your domain
  4. +
  5. + Create an API Key and add to{" "} + + .env + +
  6. +
  7. + Update "from:" in{" "} + + app/api/email/route.ts + +
  8. +
  9. Send email 🎉
  10. +
+
+
+
e.preventDefault()} + className="space-y-3 pt-4 border-t mt-4" + > + {errors && ( +

{JSON.stringify(errors, null, 2)}

+ )} +
+ + +
+
+ + +
+ +
+
+ ); +} + diff --git a/src/app/api/email/route.ts b/src/app/api/email/route.ts new file mode 100644 index 0000000..f3920e2 --- /dev/null +++ b/src/app/api/email/route.ts @@ -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 ", + to: [email], + subject: "Hello world!", + react: EmailTemplate({ firstName: name }), + text: "Email powered by Resend.", + }); + + return NextResponse.json(data); + } catch (error) { + return NextResponse.json({ error }); + } +} diff --git a/src/components/emails/FirstEmail.tsx b/src/components/emails/FirstEmail.tsx new file mode 100644 index 0000000..cecff1b --- /dev/null +++ b/src/components/emails/FirstEmail.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; + +interface EmailTemplateProps { + firstName: string; +} + +export const EmailTemplate: React.FC> = ({ + firstName, +}) => ( +
+

Welcome, {firstName}!

+

+ 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. +

+
+

Sent with help from Resend and Kirimase 😊

+
+); diff --git a/src/lib/email/index.ts b/src/lib/email/index.ts new file mode 100644 index 0000000..82b7264 --- /dev/null +++ b/src/lib/email/index.ts @@ -0,0 +1,4 @@ +import { Resend } from "resend"; +import { env } from "@/lib/env.mjs"; + +export const resend = new Resend(env.RESEND_API_KEY); diff --git a/src/lib/email/utils.ts b/src/lib/email/utils.ts new file mode 100644 index 0000000..8b1eec3 --- /dev/null +++ b/src/lib/email/utils.ts @@ -0,0 +1,6 @@ +import { z } from "zod"; + +export const emailSchema = z.object({ + name: z.string().min(3), + email: z.string().email(), +}); diff --git a/src/lib/env.mjs b/src/lib/env.mjs index 2b4f3cd..c3546c5 100644 --- a/src/lib/env.mjs +++ b/src/lib/env.mjs @@ -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), @@ -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,