Prompt

I'm building a Next.js app and want a simple production-ready email setup.

Technology / Api platforms1 observationLast seen Sep 24, 2026

Latest observation

Sep 24, 2026OpenAI APIWeb search: off

A simple, production-ready email setup for a Next.js app is usually:

  • Send emails from server-side only via a Route Handler / Server Action
  • Use a transactional email provider like Resend, Postmark, SendGrid, or Mailgun
  • Render emails as HTML with a reusable template component
  • Protect the endpoint with validation + rate limiting
  • Use environment variables for secrets
  • Add logging and error handling

Recommended stack

If you want “simple and modern,” I’d suggest:

  • Resend for sending
  • React Email for templates
  • Zod for validation
  • Next.js Route Handler for the API endpoint

This is easy to maintain and works well in production.


1) Install dependencies

npm install resend zod
npm install @react-email/components react-email

If you’re using TypeScript, you already have the basics in Next.js.


2) Add environment variables

RESEND_API_KEY=your_resend_key
EMAIL_FROM="Your App <no-reply@yourdomain.com>"
EMAIL_TO=you@yourdomain.com

Make sure EMAIL_FROM uses a domain verified in your email provider.


3) Create the email template

Example: emails/contact-email.tsx

import {
  Body,
  Container,
  Head,
  Html,
  Preview,
  Section,
  Text,
} from "@react-email/components";

interface ContactEmailProps {
  name: string;
  email: string;
  message: string;
}

export function ContactEmail({ name, email, message }: ContactEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>New contact form submission</Preview>
      <Body style={{ backgroundColor: "#f6f9fc", fontFamily: "Arial, sans-serif" }}>
        <Container style={{ backgroundColor: "#ffffff", padding: "24px" }}>
          <Section>
            <Text><strong>Name:</strong> {name}</Text>
            <Text><strong>Email:</strong> {email}</Text>
            <Text><strong>Message:</strong></Text>
            <Text>{message}</Text>
          </Section>
        </Container>
      </Body>
    </Html>
  );
}

4) Create the API route

Example: app/api/contact/route.ts

import { Resend } from "resend";
import { z } from "zod";
import { ContactEmail } from "@/emails/contact-email";
import { render } from "@react-email/components";

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

const schema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  message: z.string().min(1).max(5000),
});

export async function POST(req: Request) {
  try {
    const body = await req.json();
    const parsed = schema.safeParse(body);

    if (!parsed.success) {
      return Response.json(
        { error: "Invalid input", issues: parsed.error.flatten() },
        { status: 400 }
      );
    }

    const { name, email, message } = parsed.data;

    const html = render(
      ContactEmail({
        name,
        email,
        message,
      })
    );

    const result = await resend.emails.send({
      from: process.env.EMAIL_FROM!,
      to: process.env.EMAIL_TO!,
      replyTo: email,
      subject: `New contact form message from ${name}`,
      html,
    });

    return Response.json({ success: true, id: result.data?.id });
  } catch (error) {
    console.error("Email send error:", error);
    return Response.json(
      { error: "Failed to send email" },
      { status: 500 }
    );
  }
}

5) Add a simple form on the client

Example: app/contact/page.tsx

"use client";

import { useState } from "react";

export default function ContactPage() {
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState<string | null>(null);

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setLoading(true);
    setStatus(null);

    const formData = new FormData(e.currentTarget);
    const payload = {
      name: formData.get("name"),
      email: formData.get("email"),
      message: formData.get("message"),
    };

    const res = await fetch("/api/contact", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });

    const data = await res.json();

    if (res.ok) {
      setStatus("Message sent!");
      e.currentTarget.reset();
    } else {
      setStatus(data.error ?? "Something went wrong");
    }

    setLoading(false);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button type="submit" disabled={loading}>
        {loading ? "Sending..." : "Send"}
      </button>
      {status && <p>{status}</p>}
    </form>
  );
}

Production tips

1) Add rate limiting

Prevent spam by limiting requests per IP. You can use:

  • Upstash Rate Limit
  • Vercel middleware
  • Cloudflare
  • a simple DB-backed limiter

2) Add bot protection

Use one of:

  • CAPTCHA / Turnstile
  • honeypot field
  • invisible anti-bot checks

3) Verify your sender domain

This reduces spam filtering and improves deliverability.

4) Keep sending server-side

Never expose your API key in the browser.

5) Use a dedicated transactional inbox

Send contact form emails to a monitored address, not a personal inbox.


If you want an even simpler option

If you don’t want templates yet, you can just send plain text or a minimal HTML string. But for production, the setup above is a good balance of simple and robust.

If you want, I can also give you:

  1. a copy-paste full Next.js contact form setup,
  2. a server action version, or
  3. a Resend + Tailwind email template.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.