← back to Trademarks Copyright

src/app/api/drops/unsubscribe/[token]/route.ts

40 lines

import { NextRequest, NextResponse } from "next/server";
import { query } from "@/lib/db";

async function unsubscribe(token: string) {
  const { rowCount } = await query(
    `UPDATE subscribers SET status = 'cancelled', cancelled_at = NOW() WHERE token = $1`,
    [token]
  ) as unknown as { rowCount: number };
  return rowCount > 0;
}

// POST is required by Gmail/Yahoo one-click unsubscribe (RFC 8058).
export async function POST(_req: NextRequest, { params }: { params: Promise<{ token: string }> }) {
  const { token } = await params;
  const ok = await unsubscribe(token);
  if (!ok) return NextResponse.json({ error: "not found" }, { status: 404 });
  return NextResponse.json({ ok: true });
}

// GET supports email-client clickthroughs that don't POST.
// Returns a confirmation page rather than JSON.
export async function GET(_req: NextRequest, { params }: { params: Promise<{ token: string }> }) {
  const { token } = await params;
  const ok = await unsubscribe(token);
  const html = ok
    ? `<!doctype html><html><body style="font-family:Georgia,serif;max-width:500px;margin:80px auto;padding:20px;text-align:center;">
         <h1>Unsubscribed.</h1>
         <p>You won't receive any more drops. Sorry to see you go.</p>
         <p style="font-size:13px;color:#888;">If this was an accident, <a href="/drops">sign up again</a>.</p>
       </body></html>`
    : `<!doctype html><html><body style="font-family:Georgia,serif;max-width:500px;margin:80px auto;padding:20px;text-align:center;">
         <h1>Unsubscribe link invalid.</h1>
         <p>This token wasn't recognized. If you still receive drops, reply directly and we'll remove you.</p>
       </body></html>`;
  return new NextResponse(html, {
    status: ok ? 200 : 404,
    headers: { "content-type": "text/html" },
  });
}