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 let threadId = headers['X-Thread-ID'] || (inReplyTo ? inReplyTo.replace(/<|>/g, '').split('@')[0] : null); // If no thread ID, this is a NEW direct email (not a reply) const isNewEmail = !threadId; if (isNewEmail) { edgeLogger.info('New direct email received (no thread ID)', { requestId: tracking.requestId, from, subject, messageId }); } // Find or create submission let submission = null; let submissionError = null; if (isNewEmail) { // Extract sender email const senderEmail = from.match(/<(.+)>/)?.[1] || from; const senderName = from.match(/^(.+?)\s*/)?.[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 senderEmail = from.match(/<(.+)>/)?.[1] || from; const { error: insertError } = await supabase .from('contact_email_threads') .insert({ submission_id: submission.id, message_id: messageId, in_reply_to: inReplyTo || null, 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, is_new_ticket: isNewEmail } }); 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);