diff --git a/src/routes/hooks/poll-imap.ts b/src/routes/hooks/poll-imap.ts index fbbde7f..ce15c03 100644 --- a/src/routes/hooks/poll-imap.ts +++ b/src/routes/hooks/poll-imap.ts @@ -8,6 +8,10 @@ import { simpleParser } from "mailparser"; import { matchAndUpdateEmails } from "@/lib/email-matching.server"; const MAX_MESSAGES_PER_RUN = 50; +// Cloudflare Workers drop long-lived TLS sockets when streaming very large +// IMAP FETCH responses. Skip oversize messages (>5 MB) so a single huge +// email doesn't kill the whole poll. They can be retrieved manually later. +const MAX_MESSAGE_SIZE_BYTES = 5 * 1024 * 1024; export const Route = createFileRoute("/hooks/poll-imap")({ server: { @@ -51,13 +55,48 @@ export const Route = createFileRoute("/hooks/poll-imap")({ const sinceUid = (settings.last_uid ?? 0) + 1; const range = `${sinceUid}:*`; - for await (const msg of client.fetch( + // First pass: fetch lightweight envelopes only to learn the + // size of each message. Then fetch full source one-by-one, + // skipping anything over the size cap. + const candidates: Array<{ uid: number; size: number }> = []; + for await (const meta of client.fetch( range, - { uid: true, source: true, envelope: true, size: true }, + { uid: true, size: true }, { uid: true }, )) { + if (meta.uid <= (settings.last_uid ?? 0)) continue; + candidates.push({ uid: meta.uid, size: meta.size ?? 0 }); + if (candidates.length >= MAX_MESSAGES_PER_RUN * 4) break; + } + + for (const cand of candidates) { if (imported >= MAX_MESSAGES_PER_RUN) break; - if (msg.uid <= (settings.last_uid ?? 0)) continue; + + // Always advance highestUid so we don't re-attempt skipped messages. + if (cand.uid > highestUid) highestUid = cand.uid; + + if (cand.size > MAX_MESSAGE_SIZE_BYTES) { + console.warn( + `Skipping IMAP uid ${cand.uid} (${cand.size} bytes > ${MAX_MESSAGE_SIZE_BYTES})`, + ); + continue; + } + + let msg: { uid: number; source: Buffer; size?: number } | null = null; + try { + for await (const m of client.fetch( + String(cand.uid), + { uid: true, source: true, size: true }, + { uid: true }, + )) { + msg = m as any; + break; + } + } catch (e) { + console.error("Per-message fetch failed uid", cand.uid, e); + continue; + } + if (!msg) continue; let parsed; try {