Implementing a Double Opt-In Subscription Flow
To ensure subscriber list quality and avoid spam honeypots, implement a Double Opt-In flow.
1. Double Opt-In Flow Architecture
graph TD
A[Visitor submits email] --> B[Generate encrypted token link]
B --> C[Send verification email via Resend]
C --> D[Visitor clicks link in email]
D --> E[Server verifies token signature]
E --> F[API adds email to Resend Audience]2. Part 1: Sending the Verification Link
When a visitor submits the subscription form, generate a verification token (save it temporarily with an expiration timestamp) and mail the link:
// app/api/subscribe/route.ts
import { resend } from "../../lib/resend";
import { prisma } from "../../lib/prisma";
import crypto from "crypto";
export async function POST(req: Request) {
const { email } = await req.json();
if (!email) return new Response("Email required", { status: 400 });
// 1. Generate a random secure token
const token = crypto.randomBytes(32).toString("hex");
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hour expiry
// 2. Save pending verification state in database
await prisma.pendingSubscription.create({
data: { email, token, expiresAt },
});
const confirmUrl = `https://mycompany.com/api/subscribe/confirm?token=${token}`;
// 3. Send validation email
await resend.emails.send({
from: "Newsletter <newsletter@mycompany.com>",
to: [email],
subject: "Confirm your subscription",
html: `<p>Please click the link below to confirm your subscription:</p><a href="${confirmUrl}">Confirm Subscription</a>`,
});
return new Response("Verification email sent", { status: 200 });
}3. Part 2: Verifying the Token and Adding Contact
Create the confirmation endpoint handler that processes the token link:
// app/api/subscribe/confirm/route.ts
import { resend } from "../../../lib/resend";
import { prisma } from "../../../lib/prisma";
export async function GET(req: Request) {
const url = new URL(req.url);
const token = url.searchParams.get("token");
if (!token) return new Response("Missing token", { status: 400 });
// 1. Fetch pending token details
const pending = await prisma.pendingSubscription.findUnique({
where: { token },
});
if (!pending || pending.expiresAt < new Date()) {
return new Response("Invalid or expired token", { status: 400 });
}
// 2. Add email to Resend Audience list
const audienceId = process.env.RESEND_AUDIENCE_ID as string;
await resend.contacts.create({
audienceId,
email: pending.email,
});
// 3. Clean up database entry
await prisma.pendingSubscription.delete({
where: { token },
});
// Redirect to success landing page
return Response.redirect("https://mycompany.com/subscription-success");
}Published on Last updated: