Document Intake and Routing
Module 17 · business · intermediate
Solutions 17 · Document Intake and Routing
Exercise 1
Replace the No Operation node with Gmail (or Slack):
- Subject:
{{ $json.needs_review ? '📄 Please review: ' + $json.original_name : '⚠️ Filing failed: ' + $json.original_name }} - Message:
{{ $json.needs_review ? 'Reason: ' + $json.review_reason : 'Error: ' + $json.upload_error }}
Best guess: {{ $json.type_label }} ({{ $json.confidence }}), {{ $json.fields.counterparty || 'unknown sender' }}
{{ $json.file_link ? 'File: ' + $json.file_link : '' }}
Exercise 2
New trigger branch:
- Gmail Trigger: poll every minute, Filters → Search
to:docs@yourfirm.com has:attachment filename:pdf, Simplify off, Options → Download Attachments on. - Code
One Item per PDF:const out = []; for (const item of $input.all()) for (const [key, file] of Object.entries(item.binary ?? {})) if (file.mimeType === 'application/pdf') out.push({ json: { from: item.json.from?.value?.[0]?.address ?? '', subject: item.json.subject }, binary: { file } }); return out; - Generalize the three places that read
$('New Document'): theHas PDF?expression, the Extract From File binary field, andAttach File. Read from the current item's binary instead ($binaryin expressions,$input.first().binaryin Code), and carry the binary through Rule Classifier by returningbinary: $input.first().binarythere.
For several attachments per email, keep the per-item pairing ($input.all() + index) instead of .first() in the Code nodes. This solution hasn't been run against a live inbox yet.
Exercise 3
After Rule Classifier, add IF Scanned? ({{ $json.text_length < 20 }}):
- true → OpenAI node: Resource Image → Analyze Image (model with vision, e.g.
gpt-5-mini), Input Type Binary, binary from the upload. Prompt: "Transcribe all text in this document exactly." For multi-page PDFs, convert pages to images first (e.g. a small conversion service), since the analyze-image operation expects images. - → Code
Use OCR Text: settextto the transcription and re-run the rule logic (or go straight to AI Classify).
Not run against a real scanned file; check the OpenAI node's output field for the transcription in your n8n version.
Exercise 4
After Upload Result (and on the dry-run branch if you want to log those too), Google Sheets → Append Row to Document Log:
| Column | Value |
|---|---|
| received_at | {{ $now.toISO() }} |
| original_name | {{ $json.original_name }} |
| filename | {{ $json.filename }} |
| doc_type | {{ $json.doc_type }} |
| confidence | {{ $json.confidence }} |
| method | {{ $json.method }} |
| counterparty | {{ $json.fields.counterparty }} |
| amount | {{ $json.fields.amount }} |
| folder | {{ $json.folder_key }} |
| link | {{ $json.file_link }} |
Turn on Continue on Error for the Sheets node too, so a logging problem never blocks filing.