import { NextResponse } from 'next/server'; import nodemailer from 'nodemailer'; const transporter = nodemailer.createTransport({ sendmail: true, newline: 'unix', path: '/usr/sbin/sendmail', }); export async function POST(req: Request) { try { const { name, email, message, company, // honeypot turnstileToken, formStartedAt, } = await req.json(); // Basic validation if (!name || !email || !message) { return NextResponse.json( { error: 'Missing required fields.' }, { status: 400 }, ); } // Honeypot: bots often fill this if (company && String(company).trim().length > 0) { return NextResponse.json({ error: 'Spam detected.' }, { status: 400 }); } // Time-based spam check const started = Number(formStartedAt); if (!started || Date.now() - started < 3000) { return NextResponse.json( { error: 'Submission too fast.' }, { status: 400 }, ); } // Turnstile validation if (!turnstileToken) { return NextResponse.json( { error: 'Missing captcha token.' }, { status: 400 }, ); } const secret = process.env.TURNSTILE_SECRET_KEY; if (!secret) { return NextResponse.json( { error: 'Turnstile secret not configured.' }, { status: 500 }, ); } const ip = req.headers.get('cf-connecting-ip') || ''; const verifyRes = await fetch( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ secret, response: turnstileToken, remoteip: ip, }), }, ); const verifyData = (await verifyRes.json()) as { success: boolean; 'error-codes'?: string[]; }; if (!verifyData.success) { return NextResponse.json( { error: 'Captcha verification failed.', details: verifyData['error-codes'] || [], }, { status: 400 }, ); } // Send email via local sendmail (Postfix/Sendmail/Exim) await transporter.sendMail({ from: 'William March ', to: 'your-real-inbox@example.com', // TODO: change to your inbox replyTo: email, subject: `New message from ${name} via williammarch.xyz`, text: `From: ${name} <${email}>\n\n${message}`, html: `

From: ${name} <${email}>

Message:

${message.replace(/\n/g, '
')}

`, }); return NextResponse.json({ ok: true }); } catch (error) { const msg = error instanceof Error ? error.message : 'Failed to send email.'; return NextResponse.json({ error: msg }, { status: 500 }); } }