import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; import { createErrorResponse } from "../_shared/errorSanitizer.ts"; const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; interface InboundEmailPayload { from: string; to: string; subject: string; text: string; html?: string; messageId: string; inReplyTo?: string; references?: string[]; headers: Record; } const handler = async (req: Request): Promise => { if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } const tracking = startRequest(); try { const supabase = createClient( Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! ); const payload: InboundEmailPayload = await req.json(); const { from, to, subject, text, html, messageId, inReplyTo, references, headers } = payload; edgeLogger.info('Inbound email received', { requestId: tracking.requestId, from, to, messageId }); // Extract thread ID from headers or inReplyTo const threadId = headers['X-Thread-ID'] || (inReplyTo ? inReplyTo.replace(/<|>/g, '').split('@')[0] : null); if (!threadId) { edgeLogger.warn('Email missing thread ID', { requestId: tracking.requestId, messageId }); return new Response(JSON.stringify({ success: false, reason: 'no_thread_id' }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // Find submission by thread_id const { data: submission, error: submissionError } = await supabase .from('contact_submissions') .select('id, email, status') .eq('thread_id', threadId) .single(); if (submissionError || !submission) { edgeLogger.warn('Submission not found for thread ID', { requestId: tracking.requestId, threadId }); return new Response(JSON.stringify({ success: false, reason: 'submission_not_found' }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // Verify sender email matches const senderEmail = from.match(/<(.+)>/)?.[1] || from; if (senderEmail.toLowerCase() !== submission.email.toLowerCase()) { edgeLogger.warn('Sender email mismatch', { requestId: tracking.requestId, expected: submission.email, received: senderEmail }); return new Response(JSON.stringify({ success: false, reason: 'email_mismatch' }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }); } // Insert email thread record const { error: insertError } = await supabase .from('contact_email_threads') .insert({ submission_id: submission.id, message_id: messageId, in_reply_to: inReplyTo, reference_chain: references || [], from_email: senderEmail, to_email: to, subject, body_text: text, body_html: html, direction: 'inbound', metadata: { received_at: new Date().toISOString(), headers: headers } }); if (insertError) { edgeLogger.error('Failed to insert inbound email thread', { requestId: tracking.requestId, error: insertError }); return createErrorResponse(insertError, 500, corsHeaders); } // Update submission status if pending if (submission.status === 'pending') { await supabase .from('contact_submissions') .update({ status: 'in_progress' }) .eq('id', submission.id); } edgeLogger.info('Inbound email processed successfully', { requestId: tracking.requestId, submissionId: submission.id, duration: endRequest(tracking) }); return new Response( JSON.stringify({ success: true }), { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } ); } catch (error) { edgeLogger.error('Unexpected error in receive-inbound-email', { requestId: tracking.requestId, error: error instanceof Error ? error.message : String(error) }); return createErrorResponse(error, 500, corsHeaders); } }; serve(handler);