type Env = { TURNSTILE_SECRET_KEY?: string; AZURE_COMMUNICATION_CONNECTION_STRING?: string; CONTACT_FROM_EMAIL?: string; CONTACT_TO_EMAIL?: string; }; const jsonResponse = (data: Record, status = 200) => new Response(JSON.stringify(data), { status, headers: { "content-type": "application/json" }, }); const readFormField = (value: FormDataEntryValue | null) => typeof value === "string" ? value.trim() : ""; const parseConnectionString = (connectionString: string) => { const parts = connectionString.split(";").filter(Boolean); const map = new Map(); for (const part of parts) { const [rawKey, ...rest] = part.split("="); const key = rawKey?.trim().toLowerCase(); const value = rest.join("=").trim(); if (key && value) { map.set(key, value); } } return { endpoint: map.get("endpoint") ?? "", accessKey: map.get("accesskey") ?? map.get("access_key") ?? "", }; }; const handlePost: PagesFunction = async ({ request, env }) => { const formData = await request.formData(); const name = readFormField(formData.get("name")); const email = readFormField(formData.get("email")); const message = readFormField(formData.get("message")); const turnstileToken = readFormField(formData.get("cf-turnstile-response")); if (!name || !email || !message) { return jsonResponse( { ok: false, error: "Missing required fields." }, 400, ); } if (!env.TURNSTILE_SECRET_KEY) { return jsonResponse( { ok: false, error: "Turnstile secret key not configured." }, 500, ); } if (!turnstileToken) { return jsonResponse( { ok: false, error: "Turnstile verification failed." }, 400, ); } const verifyBody = new URLSearchParams({ secret: env.TURNSTILE_SECRET_KEY, response: turnstileToken, remoteip: request.headers.get("CF-Connecting-IP") ?? "", }); const verifyResponse = await fetch( "https://challenges.cloudflare.com/turnstile/v0/siteverify", { method: "POST", body: verifyBody, }, ); const verifyResult = (await verifyResponse.json()) as { success?: boolean; }; if (!verifyResult.success) { return jsonResponse( { ok: false, error: "Turnstile verification failed." }, 400, ); } if (!env.AZURE_COMMUNICATION_CONNECTION_STRING) { return jsonResponse( { ok: false, error: "Email provider not configured." }, 500, ); } const fromEmail = env.CONTACT_FROM_EMAIL; if (!fromEmail) { return jsonResponse( { ok: false, error: "Sender email not configured." }, 500, ); } const toEmail = env.CONTACT_TO_EMAIL ?? "calum@muir.in"; const { endpoint, accessKey } = parseConnectionString( env.AZURE_COMMUNICATION_CONNECTION_STRING, ); if (!endpoint || !accessKey) { return jsonResponse( { ok: false, error: "Email provider not configured." }, 500, ); } const emailPayload = { senderAddress: fromEmail, content: { subject: "New contact form submission", plainText: `Name: ${name}\nEmail: ${email}\n\n${message}`, }, recipients: { to: [{ address: toEmail }], }, replyTo: [{ address: email }], }; const apiBase = endpoint.replace(/\/$/, ""); const response = await fetch( apiBase + "/emails:send?api-version=2023-03-31", { method: "POST", headers: { "Content-Type": "application/json", "api-key": accessKey, }, body: JSON.stringify(emailPayload), }, ); if (!response.ok) { const errorText = await response.text(); return jsonResponse( { ok: false, error: "Failed to send email.", details: errorText.slice(0, 500), }, 502, ); } return jsonResponse({ ok: true }); }; export const onRequest: PagesFunction = async (context) => { if (context.request.method !== "POST") { return jsonResponse({ ok: false, error: "Method not allowed." }, 405); } return handlePost(context); };