diff --git a/src/routes/hooks/poll-imap.ts b/src/routes/hooks/poll-imap.ts index 3a99242..a51733c 100644 --- a/src/routes/hooks/poll-imap.ts +++ b/src/routes/hooks/poll-imap.ts @@ -5,6 +5,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { supabaseAdmin } from "@/integrations/supabase/client.server"; import { ImapFlow } from "imapflow"; import { simpleParser } from "mailparser"; +import { matchAndUpdateEmails } from "@/lib/email-matching.server"; const MAX_MESSAGES_PER_RUN = 50; @@ -82,6 +83,51 @@ export const Route = createFileRoute("/hooks/poll-imap")({ : null; const attachments = parsed.attachments ?? []; + // Upload attachments to storage; collect metadata. + const attachmentMeta: Array<{ + filename: string; + mime_type: string; + size_bytes: number; + storage_path: string; + }> = []; + for (let i = 0; i < attachments.length; i++) { + const att = attachments[i]; + const filename = + att.filename || `attachment-${i + 1}.bin`; + const safeName = filename.replace(/[^\w.\-]+/g, "_"); + const path = `${msg.uid}/${Date.now()}-${i}-${safeName}`; + try { + const { error: upErr } = await supabaseAdmin.storage + .from("email-attachments") + .upload(path, att.content as Buffer, { + contentType: att.contentType || "application/octet-stream", + upsert: true, + }); + if (upErr) { + console.error("Attachment upload failed", filename, upErr.message); + continue; + } + attachmentMeta.push({ + filename, + mime_type: att.contentType || "application/octet-stream", + size_bytes: att.size ?? (att.content as Buffer)?.length ?? 0, + storage_path: path, + }); + } catch (e) { + console.error("Attachment store error", filename, e); + } + } + + // Threading headers + const inReplyTo = + (parsed.inReplyTo as string | undefined) ?? null; + const refsRaw = (parsed.references as string | string[] | undefined) ?? []; + const references = Array.isArray(refsRaw) + ? refsRaw + : refsRaw + ? [refsRaw] + : []; + const insertRow = { message_id: parsed.messageId ?? null, imap_uid: msg.uid, @@ -94,20 +140,27 @@ export const Route = createFileRoute("/hooks/poll-imap")({ body_text: text, body_html: parsed.html || null, snippet, - has_attachments: attachments.length > 0, - attachment_count: attachments.length, + has_attachments: attachmentMeta.length > 0, + attachment_count: attachmentMeta.length, + attachments: attachmentMeta, + in_reply_to: inReplyTo, + email_references: references, raw_size_bytes: msg.size ?? null, }; // Upsert by message_id to avoid duplicates if mailbox is re-polled - const { error: insErr } = await supabaseAdmin + const { data: insertedRows, error: insErr } = await supabaseAdmin .from("incoming_emails") - .upsert(insertRow, { onConflict: "message_id", ignoreDuplicates: true }); + .upsert(insertRow, { onConflict: "message_id", ignoreDuplicates: true }) + .select("id"); if (insErr && !insErr.message.includes("duplicate")) { console.error("Insert failed for uid", msg.uid, insErr.message); } else { imported++; + if (insertedRows && insertedRows.length > 0) { + newEmailIds.push(insertedRows[0].id); + } } if (msg.uid > highestUid) highestUid = msg.uid;