Skip to main content
Back to Blog
Tutorials
3 min read
December 21, 2024

How to Implement Email Sending with Templates in Next.js

Build a complete transactional email system in Next.js using Resend and React Email with reusable templates, queuing, and tracking.

Ryel Banfield

Founder & Lead Developer

Transactional emails are critical for user onboarding, notifications, and receipts. Here is how to build a robust email system.

Setup

pnpm add resend @react-email/components
// lib/email.ts
import { Resend } from "resend";

export const resend = new Resend(process.env.RESEND_API_KEY);

export const FROM_EMAIL = "notifications@yourdomain.com";
export const REPLY_TO = "support@yourdomain.com";

Base Email Layout

// emails/BaseLayout.tsx
import {
  Body,
  Container,
  Head,
  Hr,
  Html,
  Img,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components";

interface BaseLayoutProps {
  preview: string;
  children: React.ReactNode;
}

export function BaseLayout({ preview, children }: BaseLayoutProps) {
  return (
    <Html>
      <Head />
      <Preview>{preview}</Preview>
      <Body style={body}>
        <Container style={container}>
          <Section style={header}>
            <Img
              src="https://yourdomain.com/logo.png"
              width={120}
              height={32}
              alt="Company"
            />
          </Section>
          {children}
          <Hr style={hr} />
          <Section style={footer}>
            <Text style={footerText}>
              Your Company, 123 Main St, City, State 12345
            </Text>
            <Text style={footerText}>
              <Link href="https://yourdomain.com/unsubscribe" style={link}>
                Unsubscribe
              </Link>
              {" | "}
              <Link href="https://yourdomain.com/preferences" style={link}>
                Email preferences
              </Link>
            </Text>
          </Section>
        </Container>
      </Body>
    </Html>
  );
}

const body = { backgroundColor: "#f6f9fc", fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" };
const container = { backgroundColor: "#ffffff", margin: "0 auto", padding: "20px 0 48px", maxWidth: "580px" };
const header = { padding: "20px 32px" };
const hr = { borderColor: "#e6ebf1", margin: "20px 0" };
const footer = { padding: "0 32px" };
const footerText = { color: "#8898aa", fontSize: "12px", lineHeight: "16px" };
const link = { color: "#556cd6", textDecoration: "underline" };

Welcome Email Template

// emails/WelcomeEmail.tsx
import { Button, Heading, Section, Text } from "@react-email/components";
import { BaseLayout } from "./BaseLayout";

interface WelcomeEmailProps {
  name: string;
  loginUrl: string;
}

export function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) {
  return (
    <BaseLayout preview={`Welcome to our platform, ${name}`}>
      <Section style={{ padding: "0 32px" }}>
        <Heading as="h1" style={{ fontSize: "24px", marginBottom: "16px" }}>
          Welcome, {name}!
        </Heading>
        <Text style={{ fontSize: "16px", lineHeight: "24px", color: "#525f7f" }}>
          Thank you for signing up. Your account is ready and waiting for you.
        </Text>
        <Text style={{ fontSize: "16px", lineHeight: "24px", color: "#525f7f" }}>
          Here is what you can do next:
        </Text>
        <ul>
          <li>Complete your profile</li>
          <li>Create your first project</li>
          <li>Invite team members</li>
        </ul>
        <Button
          href={loginUrl}
          style={{
            backgroundColor: "#556cd6",
            borderRadius: "5px",
            color: "#fff",
            fontSize: "16px",
            fontWeight: "bold",
            textDecoration: "none",
            textAlign: "center" as const,
            display: "block",
            padding: "12px 20px",
            marginTop: "16px",
          }}
        >
          Go to dashboard
        </Button>
      </Section>
    </BaseLayout>
  );
}

Password Reset Template

// emails/PasswordResetEmail.tsx
import { Button, Heading, Section, Text } from "@react-email/components";
import { BaseLayout } from "./BaseLayout";

interface PasswordResetEmailProps {
  name: string;
  resetUrl: string;
  expiresIn: string;
}

export function PasswordResetEmail({
  name,
  resetUrl,
  expiresIn,
}: PasswordResetEmailProps) {
  return (
    <BaseLayout preview="Reset your password">
      <Section style={{ padding: "0 32px" }}>
        <Heading as="h1" style={{ fontSize: "24px", marginBottom: "16px" }}>
          Password reset
        </Heading>
        <Text style={{ fontSize: "16px", lineHeight: "24px", color: "#525f7f" }}>
          Hi {name}, we received a request to reset your password. Click the
          button below to set a new one. This link expires in {expiresIn}.
        </Text>
        <Button
          href={resetUrl}
          style={{
            backgroundColor: "#556cd6",
            borderRadius: "5px",
            color: "#fff",
            fontSize: "16px",
            fontWeight: "bold",
            textDecoration: "none",
            textAlign: "center" as const,
            display: "block",
            padding: "12px 20px",
          }}
        >
          Reset password
        </Button>
        <Text style={{ fontSize: "14px", color: "#8898aa", marginTop: "16px" }}>
          If you did not request this, you can safely ignore this email.
        </Text>
      </Section>
    </BaseLayout>
  );
}

Email Service

// lib/email-service.ts
import { resend, FROM_EMAIL, REPLY_TO } from "./email";
import { WelcomeEmail } from "@/emails/WelcomeEmail";
import { PasswordResetEmail } from "@/emails/PasswordResetEmail";
import { createElement } from "react";

type EmailType = "welcome" | "password-reset" | "invoice" | "notification";

interface SendEmailOptions {
  to: string | string[];
  subject: string;
  react: React.ReactElement;
  tags?: { name: string; value: string }[];
}

async function sendEmail({ to, subject, react, tags }: SendEmailOptions) {
  const { data, error } = await resend.emails.send({
    from: FROM_EMAIL,
    to: Array.isArray(to) ? to : [to],
    replyTo: REPLY_TO,
    subject,
    react,
    tags,
  });

  if (error) {
    console.error("Email send failed:", error);
    throw new Error(`Failed to send email: ${error.message}`);
  }

  return data;
}

export async function sendWelcomeEmail(email: string, name: string) {
  return sendEmail({
    to: email,
    subject: `Welcome to our platform, ${name}!`,
    react: createElement(WelcomeEmail, {
      name,
      loginUrl: `${process.env.NEXT_PUBLIC_URL}/dashboard`,
    }),
    tags: [{ name: "type", value: "welcome" }],
  });
}

export async function sendPasswordResetEmail(
  email: string,
  name: string,
  token: string
) {
  return sendEmail({
    to: email,
    subject: "Reset your password",
    react: createElement(PasswordResetEmail, {
      name,
      resetUrl: `${process.env.NEXT_PUBLIC_URL}/reset-password?token=${token}`,
      expiresIn: "1 hour",
    }),
    tags: [{ name: "type", value: "password-reset" }],
  });
}

// Batch send
export async function sendBatchEmails(
  emails: { to: string; subject: string; react: React.ReactElement }[]
) {
  const batch = emails.map((e) => ({
    from: FROM_EMAIL,
    to: e.to,
    subject: e.subject,
    react: e.react,
  }));

  const { data, error } = await resend.batch.send(batch);
  if (error) throw new Error(`Batch send failed: ${error.message}`);
  return data;
}

API Route Usage

// app/api/auth/register/route.ts
import { NextRequest, NextResponse } from "next/server";
import { sendWelcomeEmail } from "@/lib/email-service";

export async function POST(request: NextRequest) {
  const { email, name, password } = await request.json();

  // ... create user in database ...

  // Send welcome email (non-blocking)
  sendWelcomeEmail(email, name).catch((err) =>
    console.error("Welcome email failed:", err)
  );

  return NextResponse.json({ success: true });
}

Preview Emails in Development

pnpm add -D email-dev
{
  "scripts": {
    "email:dev": "email dev --dir emails --port 3030"
  }
}

Run pnpm email:dev to preview your templates at http://localhost:3030.

Need Transactional Email Integration?

We build email systems that deliver reliably and look great in every inbox. Contact us to discuss your email needs.

emailResendReact EmailtemplatesNext.jstutorial

Ready to Start Your Project?

RCB Software builds world-class websites and applications for businesses worldwide.

Get in Touch

Related Articles