import crypto from "crypto";import { type NextRequest, NextResponse } from "next/server";const WEBHOOK_SECRET = process.env.WRAPS_WEBHOOK_SECRET!;export async function POST(request: NextRequest) { // Verify the webhook signature const signature = request.headers.get("x-wraps-signature"); if ( !signature || !crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(WEBHOOK_SECRET), ) ) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const { event, detail, messageId } = await request.json(); switch (event) { case "Bounce": { const { bounceType, bounceSubType, bouncedRecipients } = detail.bounce; // Permanent = hard bounce. Stop sending to this address, permanently. if (bounceType === "Permanent") { // These subtypes mean SES never attempted delivery — the address was // already suppressed. They do not count toward your bounce rate, and // they are not new information about the recipient. const alreadySuppressed = bounceSubType === "OnAccountSuppressionList" || bounceSubType === "OnTenantSuppressionList"; if (!alreadySuppressed) { for (const r of bouncedRecipients) { await db.contacts.update({ where: { email: r.emailAddress }, data: { bounced: true, bouncedAt: new Date() }, }); } } } // Transient = soft bounce. Do NOT suppress on the first one — a full // mailbox today is a deliverable address next week. Count them instead. if (bounceType === "Transient") { for (const r of bouncedRecipients) { await db.contacts.incrementSoftBounce(r.emailAddress); } } break; } case "Complaint": { // Someone hit "mark as spam". Unsubscribe immediately, every time. for (const r of detail.complaint.complainedRecipients) { await db.contacts.update({ where: { email: r.emailAddress }, data: { unsubscribed: true, unsubscribedAt: new Date() }, }); } break; } case "Delivery": // Note: a Delivery event does not mean the message is safe. The // receiving server can accept a message and bounce it afterward. break; } return NextResponse.json({ received: true });}