SPB Git forge

spb/hilmacorp

Public
1commits 1branches 0releases
29.2 MBsize
maindefault branch
20 days agolast push
TypeScript 93.3% JavaScript 4.4% CSS 2.3%
2.5 KB · 78 lines typescript
Raw Blame History
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