Inbound invoices: recipient-alias routing + parser hardening

parse-invoice: guard oversized PDFs (>18MB → clear "too large, saved for manual
entry" message) and surface the AI gateway's actual error instead of a bare
status code.

inbound-bill-email: route to an association by the recipient alias
(<alias>@bills.avriamail.com, via associations.inbound_alias) in addition to the
sender's vendor mapping; fix extractEmail (bare addresses were mis-split, e.g.
invoices@x → s@x); surface parse-invoice's real error in the inbox. Deployed via
MCP; migration associations_inbound_alias adds + populates the aliases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 22:13:49 -04:00
parent 386ee26a6a
commit 71cc71f89f
2 changed files with 67 additions and 12 deletions
+24 -5
View File
@@ -82,6 +82,19 @@ Deno.serve(async (req) => {
});
}
// Guard against oversized/scanned PDFs that the AI gateway rejects with an
// opaque error. The caller (inbound-bill-email) still keeps the attachment
// in the inbox for manual entry when this returns an error.
const approxBytes = Math.floor((pdf_base64.length * 3) / 4);
const sizeMB = approxBytes / (1024 * 1024);
const MAX_MB = 18;
if (sizeMB > MAX_MB) {
return new Response(
JSON.stringify({ error: `PDF too large to auto-parse (${sizeMB.toFixed(1)} MB; limit ${MAX_MB} MB). Saved to the inbox for manual entry.` }),
{ status: 413, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
}
const prompt = `You are a meticulous invoice data extraction AI. Analyze the provided PDF invoice carefully — examine EVERY page and EVERY line item, including continuation pages, sub-totals, and tables. Do not skip or summarize line items; capture each one individually exactly as it appears.
CRITICAL RULES:
@@ -162,11 +175,17 @@ Return ONLY valid JSON (no markdown, no code blocks, no commentary) with this ex
if (!response.ok) {
const errText = await response.text();
console.error("AI Gateway error:", errText);
return new Response(JSON.stringify({ error: `AI processing failed: ${response.status}` }), {
status: 502,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
console.error("AI Gateway error:", response.status, errText);
const snippet = errText.replace(/\s+/g, " ").slice(0, 300);
const hint = response.status === 429 || response.status === 402
? " (AI gateway rate/credit limit)"
: response.status === 413 || /too large|payload|size/i.test(errText)
? " (PDF too large for the AI gateway — saved to inbox for manual entry)"
: "";
return new Response(
JSON.stringify({ error: `AI gateway error ${response.status}${hint}: ${snippet || "no detail"}` }),
{ status: 502, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
}
const aiResult = await response.json();