import { createClient } from 'jsr:@supabase/supabase-js@2' import { buildInquiryNotificationEmail } from '../_shared/email.ts' const FROM_ADDRESS = 'bookings@braidsbymira.com' const MIRA_EMAIL = 'hello@braidsbymira.com' Deno.serve(async (req) => { const notifySecret = Deno.env.get('NOTIFY_WEBHOOK_SECRET') if (notifySecret && req.headers.get('X-Notify-Secret') !== notifySecret) { return new Response(JSON.stringify({ error: 'unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }) } let inquiry try { const payload = await req.json() inquiry = payload?.record if (!inquiry) { throw new Error('payload.record is missing') } } catch (err) { console.error('on-inquiry-created: failed to parse request body', err instanceof Error ? err.message : err) return new Response(JSON.stringify({ error: 'invalid request body' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }) } const supabaseAdmin = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, ) // Best-effort: records a failure so it's visible somewhere a human could // plausibly check (e.g. a future admin panel view), rather than only in // Edge Function logs nobody reads. Swallow any error from this insert // itself -- we're already in a failure path and must not let logging the // failure become a second, louder failure. const recordFailure = async (message: string) => { try { await supabaseAdmin .from('notification_failures') .insert({ kind: 'inquiry', record_id: inquiry.id, error_message: message }) } catch (err) { console.error('on-inquiry-created: failed to record notification_failures row', err instanceof Error ? err.message : err) } } try { const { data: product } = await supabaseAdmin .from('products') .select('name') .eq('id', inquiry.product_id) .single() const { subject, html } = buildInquiryNotificationEmail({ productName: product?.name ?? inquiry.product_id, contact: { name: inquiry.contact_name, email: inquiry.contact_email, notes: inquiry.contact_notes }, }) const resendApiKey = Deno.env.get('RESEND_API_KEY') if (!resendApiKey) { const message = 'RESEND_API_KEY is not set' console.error(`on-inquiry-created: ${message}, skipping email for product ${inquiry.product_id}`) await recordFailure(message) } else { const resendRes = await fetch('https://api.resend.com/emails', { method: 'POST', headers: { Authorization: `Bearer ${resendApiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from: FROM_ADDRESS, to: MIRA_EMAIL, subject, html }), }) if (!resendRes.ok) { const body = await resendRes.text() console.error( `on-inquiry-created: Resend send failed for product ${inquiry.product_id} (status ${resendRes.status})`, body, ) await recordFailure(`Resend responded ${resendRes.status}: ${body}`) } } } catch (err) { // The inquiry itself already succeeded (this handler only runs after // the row was inserted) -- a failure here means the notification email // didn't go out, not that the inquiry failed. We still return 200: // pg_net is fire-and-forget (it doesn't inspect the response status or // retry on non-2xx -- the response just lands in net._http_response), // so returning 500 wouldn't trigger a retry or help anyone; nothing // consumes this status code. What it *would* do is misrepresent the // request as failed when the inquiry itself succeeded. The actual gap // this creates -- a failed send being invisible -- is what // notification_failures (recorded above/below) exists to close. const message = err instanceof Error ? err.message : String(err) console.error(`on-inquiry-created: unexpected error for product ${inquiry?.product_id}`, message) await recordFailure(message) } return new Response(JSON.stringify({ ok: true }), { headers: { 'Content-Type': 'application/json' } }) })