A practical guide

How to set up a newsletter on your landing page

A simple, working email signup you can ship in an afternoon. No mailing-list platform required - just a tiny serverless endpoint that emails you whenever someone subscribes.

1Pick an approach

You have two realistic options for a landing-page newsletter:

This guide walks through the second approach using Resend - a developer-friendly transactional email API - and a Next.js API route. The same idea works in Express, FastAPI, SvelteKit, Astro, or any backend.

Why this works: for your first hundred subscribers, you don't need automation. You need to know who showed up. Capture the address, send yourself a notification, and graduate to a list provider when you have something to send.

2Create a Resend account

  1. Go to resend.com and sign up.
  2. Open API Keys in the sidebar.
  3. Click Create API Key, give it a name like landing-page-prod, and copy the key. You'll only see it once.

The free tier covers 100 emails/day and 3,000/month - plenty for signup notifications.

3Verify a sending domain

You can send test emails from Resend's onboarding@resend.dev address immediately, but real notifications should come from your own domain so they don't land in spam.

  1. In Resend, go to Domains → Add Domain.
  2. Enter your domain (e.g. yoursite.com).
  3. Add the DNS records Resend shows you (SPF, DKIM, and optionally DMARC) at your DNS provider.
  4. Click Verify. It usually takes a few minutes; sometimes longer.

Once verified, you can send from anything at that domain - e.g. noreply@yoursite.com.

4Add environment variables

Create a .env.local file in your project root. Never commit this - make sure .env* is in your .gitignore.

.env.local
# Get this from resend.com → API Keys
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx

# Where signup notifications go (your own inbox)
CONTACT_EMAIL=you@yourdomain.com

Heads up: when you deploy, you'll need to re-add these in your hosting platform's environment-variable settings (Vercel, Netlify, Railway, Fly, etc.). They don't get uploaded with your code, and that's a good thing.

5Build the API route

This is the whole backend. It accepts a POST with an email, validates it, and emails you a notification.

First, install the Resend SDK:

npm install resend

Then create the route. In a Next.js App Router project:

app/api/subscribe/route.ts
import { Resend } from "resend";
import { NextResponse } from "next/server";

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

export async function POST(req: Request) {
  try {
    const { email } = await req.json();

    if (!email || !email.includes("@")) {
      return NextResponse.json({ error: "Invalid email" }, { status: 400 });
    }

    await resend.emails.send({
      from: "Your Site <noreply@yourdomain.com>",
      to: process.env.CONTACT_EMAIL!,
      subject: `New signup: ${email}`,
      text: `Someone subscribed:\n\n${email}`,
    });

    return NextResponse.json({ ok: true });
  } catch (err) {
    console.error("Subscribe error:", err);
    return NextResponse.json({ error: "Failed" }, { status: 500 });
  }
}

That's the full backend. A few details worth knowing:

Not using Next.js?

The same logic in Express:

import express from "express";
import { Resend } from "resend";

const app = express();
app.use(express.json());
const resend = new Resend(process.env.RESEND_API_KEY);

app.post("/api/subscribe", async (req, res) => {
  const { email } = req.body;
  if (!email?.includes("@")) return res.status(400).json({ error: "Invalid" });
  await resend.emails.send({
    from: "Your Site <noreply@yourdomain.com>",
    to: process.env.CONTACT_EMAIL,
    subject: `New signup: ${email}`,
    text: email,
  });
  res.json({ ok: true });
});

6Add the signup form

The form just POSTs an email to /api/subscribe and shows success or error state. Here's a clean React version:

components/SignupForm.tsx
"use client";
import { useState } from "react";

export default function SignupForm() {
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<"idle" | "loading" | "ok" | "error">("idle");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!email) return;
    setStatus("loading");
    try {
      const res = await fetch("/api/subscribe", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email }),
      });
      setStatus(res.ok ? "ok" : "error");
      if (res.ok) setEmail("");
    } catch {
      setStatus("error");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        required
        placeholder="you@example.com"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        disabled={status === "loading"}
      />
      <button type="submit" disabled={status === "loading"}>
        {status === "loading" ? "Joining…" : "Subscribe"}
      </button>
      {status === "ok" && <p>Thanks - you're on the list.</p>}
      {status === "error" && <p>Something went wrong. Try again?</p>}
    </form>
  );
}

Then drop <SignupForm /> wherever you want it on your landing page.

Plain HTML version

If you're not using React, the same form works with a tiny script:

<form id="subscribe">
  <input type="email" name="email" required placeholder="you@example.com" />
  <button type="submit">Subscribe</button>
  <p id="msg"></p>
</form>
<script>
  document.getElementById("subscribe").onsubmit = async (e) => {
    e.preventDefault();
    const email = e.target.email.value;
    const res = await fetch("/api/subscribe", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    });
    document.getElementById("msg").textContent =
      res.ok ? "Thanks!" : "Try again.";
  };
</script>

7Test it locally

  1. Run your dev server (npm run dev).
  2. Open your landing page, type in your own email, hit Subscribe.
  3. Check the inbox at CONTACT_EMAIL - you should see a "New signup" message within seconds.
  4. Check the Resend dashboard's Emails tab - the send should appear there too, with delivery status.

If nothing arrives:

8Deploy

The flow is the same on any platform:

  1. Push your code to GitHub.
  2. In your hosting dashboard (Vercel, Netlify, etc.), add the same RESEND_API_KEY and CONTACT_EMAIL environment variables you used locally.
  3. Trigger a deploy. The API route runs as a serverless function automatically.

On Vercel specifically you can do this from the CLI:

vercel env add RESEND_API_KEY production
vercel env add CONTACT_EMAIL production
vercel deploy --prod

Test the live form the same way you tested locally.

9Where to go from here

Once you start getting signups, the next moves - in roughly this order:

One more thing: the smallest version of a working newsletter beats the most polished version that never ships. Get the form live this week. Worry about the welcome email and the database next week.