import { NextRequest, NextResponse } from "next/server" import prisma from "@/lib/prisma" import { sendPledgeReminder, isWhatsAppReady } from "@/lib/whatsapp" import { generateReminderContent } from "@/lib/reminders" /** * Process and send pending reminders. * Call this via cron every 15 minutes: GET /api/cron/reminders?key=SECRET * * Sends reminders that are: * 1. status = "pending" * 2. scheduledAt <= now * 3. pledge is not paid/cancelled */ export async function GET(request: NextRequest) { // Simple auth via query param or header const key = request.nextUrl.searchParams.get("key") || request.headers.get("x-cron-key") const expectedKey = process.env.CRON_SECRET || "pnpl-cron-2026" if (key !== expectedKey) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) } if (!prisma) { return NextResponse.json({ error: "No DB" }, { status: 503 }) } const now = new Date() const whatsappReady = await isWhatsAppReady() // Find pending reminders that are due const dueReminders = await prisma.reminder.findMany({ where: { status: "pending", scheduledAt: { lte: now }, pledge: { status: { notIn: ["paid", "cancelled"] }, }, }, include: { pledge: { include: { event: { select: { name: true } }, organization: { select: { name: true, bankSortCode: true, bankAccountNo: true, bankAccountName: true } }, paymentInstruction: true, }, }, }, take: 50, // Process in batches orderBy: { scheduledAt: "asc" }, }) let sent = 0 let skipped = 0 let failed = 0 const results: Array<{ id: string; status: string; channel: string; error?: string }> = [] for (const reminder of dueReminders) { const pledge = reminder.pledge const phone = pledge.donorPhone const email = pledge.donorEmail const channel = reminder.channel const daysSince = Math.floor((now.getTime() - pledge.createdAt.getTime()) / 86400000) try { // WhatsApp channel — only if donor consented if (channel === "whatsapp" && phone && whatsappReady && pledge.whatsappOptIn) { const result = await sendPledgeReminder(phone, { donorName: pledge.donorName || undefined, amountPounds: (pledge.amountPence / 100).toFixed(0), eventName: pledge.event.name, reference: pledge.reference, daysSincePledge: daysSince, step: reminder.step, }) if (result.success) { await prisma.reminder.update({ where: { id: reminder.id }, data: { status: "sent", sentAt: now }, }) sent++ results.push({ id: reminder.id, status: "sent", channel: "whatsapp" }) } else { // Try email fallback if (email) { // For now, mark as sent (email integration is external via webhook API) await prisma.reminder.update({ where: { id: reminder.id }, data: { status: "sent", sentAt: now, payload: { ...(reminder.payload as object || {}), fallback: "email", waError: result.error } }, }) sent++ results.push({ id: reminder.id, status: "sent", channel: "email-fallback" }) } else { failed++ results.push({ id: reminder.id, status: "failed", channel: "whatsapp", error: result.error }) } } } // Email channel — only if donor consented else if (channel === "email" && email && pledge.emailOptIn) { // Generate content and store for external pickup const payload = reminder.payload as Record || {} const bankDetails = pledge.paymentInstruction?.bankDetails as Record | null const content = generateReminderContent(payload.templateKey || "gentle_nudge", { donorName: pledge.donorName || undefined, amount: (pledge.amountPence / 100).toFixed(0), reference: pledge.reference, eventName: pledge.event.name, bankName: bankDetails?.bankName, sortCode: bankDetails?.sortCode, accountNo: bankDetails?.accountNo, accountName: bankDetails?.accountName, pledgeUrl: `${process.env.BASE_URL || "https://pledge.quikcue.com"}/p/${pledge.reference}`, cancelUrl: `${process.env.BASE_URL || "https://pledge.quikcue.com"}/p/${pledge.reference}?cancel=1`, }) // Mark as sent — the /api/webhooks endpoint exposes these for external email sending await prisma.reminder.update({ where: { id: reminder.id }, data: { status: "sent", sentAt: now, payload: { ...payload, generatedSubject: content.subject, generatedBody: content.body, recipientEmail: email }, }, }) sent++ results.push({ id: reminder.id, status: "sent", channel: "email" }) } // No channel available else { await prisma.reminder.update({ where: { id: reminder.id }, data: { status: "skipped" }, }) skipped++ results.push({ id: reminder.id, status: "skipped", channel, error: "No contact method" }) } } catch (err) { failed++ results.push({ id: reminder.id, status: "failed", channel, error: String(err) }) console.error(`[CRON] Reminder ${reminder.id} failed:`, err) } } return NextResponse.json({ processed: dueReminders.length, sent, skipped, failed, whatsappReady, results, nextCheck: "Call again in 15 minutes", }) }