TypeScript 93.3%
JavaScript 4.4%
CSS 2.3%
1/**2 * Author: Simon-Pierre Boucher3 * Contact: contact@spboucher.ai4 * Project: Hilmacorp.ai — Web Platform5 * File: app/api/contact/route.ts6 * Description: Contact form API route — validates input, then delivers to contact@spboucher.ai7 * via Resend when RESEND_API_KEY is set, otherwise persists to data/contact-messages.jsonl8 */910import { NextResponse } from "next/server";11import { appendFile, mkdir } from "node:fs/promises";12import path from "node:path";13import { validateContact } from "@/lib/validate";1415export const runtime = "nodejs";1617const CONTACT_TO = process.env.CONTACT_TO ?? "contact@spboucher.ai";1819async function deliverByEmail(data: {20 name: string;21 organization: string;22 email: string;23 message: string;24}): Promise<boolean> {25 const apiKey = process.env.RESEND_API_KEY;26 if (!apiKey) return false;27 const response = await fetch("https://api.resend.com/emails", {28 method: "POST",29 headers: {30 Authorization: `Bearer ${apiKey}`,31 "Content-Type": "application/json",32 },33 body: JSON.stringify({34 from: process.env.CONTACT_FROM ?? "Hilmacorp.ai <onboarding@resend.dev>",35 to: [CONTACT_TO],36 reply_to: data.email,37 subject: `[hilmacorp.ai] Contact — ${data.name}`,38 text: `Name: ${data.name}\nOrganization: ${data.organization || "—"}\nEmail: ${data.email}\n\n${data.message}`,39 }),40 });41 return response.ok;42}4344async function persistLocally(data: object): Promise<void> {45 const dir = path.join(process.cwd(), "data");46 await mkdir(dir, { recursive: true });47 const record = { receivedAt: new Date().toISOString(), to: CONTACT_TO, ...data };48 await appendFile(path.join(dir, "contact-messages.jsonl"), JSON.stringify(record) + "\n", "utf8");49}5051export async function POST(request: Request) {52 let body: unknown;53 try {54 body = await request.json();55 } catch {56 return NextResponse.json({ ok: false, errors: { body: "invalid_json" } }, { status: 400 });57 }5859 const result = validateContact(body);60 if (!result.ok || !result.data) {61 return NextResponse.json({ ok: false, errors: result.errors }, { status: 422 });62 }6364 try {65 const emailed = await deliverByEmail(result.data);66 if (!emailed) await persistLocally(result.data);67 return NextResponse.json({ ok: true });68 } catch (error) {69 console.error("[contact] delivery failed:", error);70 try {71 await persistLocally(result.data);72 return NextResponse.json({ ok: true });73 } catch {74 return NextResponse.json({ ok: false, errors: { server: "delivery_failed" } }, { status: 500 });75 }76 }77}78