I recently used the services of Resend, "The API for developers," to send email from my app. It's interesting to me because it involves very little setup, has a fairly generous free tier, and it handles all of the mail hosting: you just bring your own domain.
I wanted to quickly go over how I used it to send emails in my app. There is fairly comprehensive documentation and a quickstart guide on their website for sending emails in next.js, but I went a slightly different way.
The docs say to set up an API route that leverages the Resend sdk. So I did that, and then I realized I was handling my form submission, which used a server action to send a fetch request to my internal api, which extracted the data from the request, passed it to the sdk, and then sent a response back. That seemed like a lot of extra steps. So I ended up calling Resend directly from the server action, and it seemed to simplify things a lot.
To add this to your Next.js app, you would first need to set up an account on Resend, and generate an API key which you store in your .env file.
1. Install
npm install resend
2. Set up an Email Template
So far we haven't diverged from the quick start guide. Create a component to use as the template: components/email-template.tsx
interface EmailTemplateProps {
firstName: string;
}
export function EmailTemplate({ firstName }: EmailTemplateProps) {
return (
<div>
<h1>Welcome, {firstName}!</h1>
</div>
);
}
3. Send Email Using React
This is what I did differently from the guide. Instead of setting up an API route, create a server action in lib/actions/email.ts
"use server";
import { z } from "zod";
import { Resend } from "resend";
import { EmailTemplate } from "@/components/EmailTemplate";
import { getUserFromSession } from "@/lib/auth"
interface DataResponse {
message: string;
status: boolean;
}
const schema = z.object({
firstname: z.string({
invalid_type_error: "Invalid Email",
}),
}); //Or whatever you use to validate form data
const sendEmail = async (
formData: FormData
): Promise<DataResponse> => {
try {
const user = await getUserFromSession()
if (!user) throw new Error('Unauthorized')
//Or whatever you use to verify auth in server actions
const firstName = schema.safeParse({
firstname: formData.get("firstname"),
});
//return early if validation failed
if (!firstName.success)
return {
message: firstName.errors.flatten().fieldErrors,
status: false
};
//instantiate Resend sdk using your API key
const resend = new Resend(process.env.EMAIL_KEY);
/* You can send an email to your own email address as a
test using this 'from' address without attaching your
own domain. Once you have your domain, you would put:
from: "Your Name <your-address@your-domain.com>"" */
const { data, error } = await resend.emails.send({
from: "Acme <onboarding@resend.dev>",
to: ["delivered@resend.dev"],
subject: "Hello world",
react: EmailTemplate({ firstName }),
});
if (error) {
return { message: error.message, status: false };
}
return {
message: `Email sent to ${firstName}`,
status: false
};
} catch (error) {
return { message: error.message, status: false };
}
};
4. Try it out
Make a component with a form that uses this action:
'use client'
import { sendEmail } from '@/lib/actions'
export function SendEmailForm() {
return (
<form action={sendEmail}>
<input type="text" name="firstname" />
<button type="submit">Send Email</button>
</form>
)
}
So that's pretty much it. You can of course add a lot more functionality but this is the very basics. I just like this approach slightly better because I try not to set up API routes in my Next.js 13+ projects if I can help it. I just seems to me it kind of defeats the purpose of React Server Components.
Let me know if you have a different idea or have any comments. I hope someone found this interesting.
Top comments (0)