#!/usr/bin/env tsx /** * Test Resend Webhook Locally * * This script simulates a Resend webhook event to test your webhook handler. * * Usage: * npx tsx scripts/test-resend-webhook.ts [emailId] [eventType] * * Examples: * npx tsx scripts/test-resend-webhook.ts # Uses first email from DB * npx tsx scripts/test-resend-webhook.ts abc123 delivered # Specific email and event */ import { prisma } from "../src/lib/db"; const BASE_URL = process.env.APP_URL || "http://localhost:3001"; type EmailLogPreview = { id: string; resendMessageId: string | null; recipientEmail: string; status: string; createdAt?: Date; }; async function testWebhook() { const args = process.argv.slice(2); let emailId = args[0]; const eventType = args[1] || "email.delivered"; console.log("šŸ” Testing Resend Webhook Locally\n"); // If no emailId provided, get the most recent one from DB if (!emailId) { const recentEmail = await prisma.emailLog.findFirst({ where: { resendMessageId: { not: null } }, orderBy: { createdAt: "desc" }, select: { id: true, resendMessageId: true, recipientEmail: true, status: true, }, }); if (recentEmail?.resendMessageId) { emailId = recentEmail.resendMessageId; console.log(`šŸ“§ Found recent email:`); console.log(` ID: ${recentEmail.id}`); console.log(` Resend ID: ${recentEmail.resendMessageId}`); console.log(` Recipient: ${recentEmail.recipientEmail}`); console.log(` Current Status: ${recentEmail.status}\n`); } else { console.log("āŒ No emails found in database with resendMessageId"); console.log(" Send an email first, then run this script again.\n"); // Show all emails for debugging const allEmails = await prisma.emailLog.findMany({ take: 5, orderBy: { createdAt: "desc" }, select: { id: true, resendMessageId: true, recipientEmail: true, status: true, createdAt: true, }, }); if (allEmails.length > 0) { console.log("šŸ“‹ Recent emails in database:"); allEmails.forEach((e: EmailLogPreview) => { console.log( ` - ${e.recipientEmail} | Status: ${e.status} | ResendID: ${ e.resendMessageId || "null" }` ); }); } process.exit(1); } } // Create webhook payload (simulating Resend's format) const payload = { type: eventType, created_at: new Date().toISOString(), data: { email_id: emailId, from: "noreply@themoyos.co.za", to: ["test@example.com"], subject: "Test Email", created_at: new Date().toISOString(), }, }; console.log(`šŸ“¤ Sending ${eventType} webhook for email: ${emailId}`); console.log(` URL: ${BASE_URL}/api/webhooks/resend\n`); try { const response = await fetch(`${BASE_URL}/api/webhooks/resend`, { method: "POST", headers: { "Content-Type": "application/json", // Note: Not including svix headers since we're testing without signature verification // In production, Resend sends: svix-id, svix-timestamp, svix-signature }, body: JSON.stringify(payload), }); const responseText = await response.text(); let responseData: unknown; try { responseData = JSON.parse(responseText); } catch { responseData = responseText; } console.log(`šŸ“„ Response: ${response.status} ${response.statusText}`); console.log(` Body:`, responseData); if (response.ok) { // Check if the status was updated in the database const updatedEmail = await prisma.emailLog.findFirst({ where: { resendMessageId: emailId }, select: { id: true, status: true, lastEventAt: true }, }); if (updatedEmail) { console.log(`\nāœ… Email status updated:`); console.log(` New Status: ${updatedEmail.status}`); console.log(` Last Event: ${updatedEmail.lastEventAt}`); } } else { console.log(`\nāŒ Webhook failed`); } } catch (error: unknown) { console.error("āŒ Error calling webhook:", error); } } // Run the test testWebhook().catch((err: unknown) => console.error(err));