A few things from having done roughly this, in order of how much grief they’ll save you.
Split the one-off from the ongoing. The initial pass over 3000 files isn’t really an n8n job. It’s a script you run once, and the property you want from it is resumability: if it dies at file 1,900, you need to know which ones made it without re-reading everything. n8n will do it, but you’ll spend your evenings on execution timeouts and memory limits instead of on the corpus. Keep n8n for the part that repeats.
Keep the extracted text as files, separate from the index. This is the one I’d push hardest. A static corpus is the easy case in RAG, since there’s no incremental sync and no deletions to propagate, and you can cash that in by treating extraction and indexing as two stages with a pile of markdown sitting in between. Extraction is the expensive step. Embeddings are cheap. So when you decide three weeks in that your chunking was wrong, and you will, you rebuild from the markdown rather than going back through 30,000 pages.
Don’t trust “text only, good quality” until you’ve measured it. In any pile of 3000 there will be a few scans somebody dropped in. Worse, there’ll be some where the text extracts fine and comes out quietly mangled, because the embedded fonts declare an encoding the bytes don’t actually honour. Those are the dangerous ones: nothing errors, the garbage lands in the index, and months later the app answers confidently and wrongly about a document with no clue which one. Run a classification pass over all 3000 before you extract anything and come out with three lists (clean text, actual scans, extracted-but-suspect). It’s cheap, and this is the only moment you’ll have the whole corpus in front of you.
The spreadsheet index is ~3000 LLM calls, so make it cacheable. Generate the category, tags and summary from the extracted markdown, not from the PDF, and key each result on a hash of the source file. Then a re-run costs nothing for the files that haven’t changed. On a small Anthropic balance that’s the difference between paying once and paying every time you tweak a prompt. Same reason as above: keep it as its own pass, not welded into the ingest.
Where n8n genuinely earns its place: new file appears in SharePoint, extract, chunk, upsert to the vector store. That’s a good workflow and it’s the bit that actually recurs. The query side is nothing at your volume. Thirty people asking one question a day won’t make you think about scale even once.
The web app is the part I’d be most careful about. n8n can serve it and the chat trigger behind basic auth will get you something demo-able this week. But for 30 named users including people outside the org, you’ll likely want real sessions and per-user access before long, and that’s not what you want to be bending n8n into.
And your off-the-shelf question, honestly: for a static corpus at this query volume, a hosted knowledge-base product will probably cost you less effort than building this, and it’s worth pricing one before you commit a month. The reason to build it yourself is if it matters that the files stay in their SharePoint and that the index links back to the original document. That link-back is usually the thing the off-the-shelf ones do worst, and from your brief it sounds like it’s exactly what the NGO wants.
Disclosure so you can weigh it: I work on pdf2md, which does the PDF-to-markdown step, so I’m an interested party on that one piece. The relevant part for you probably isn’t the hosted API. There’s a CLI and a core library on npm under the same parser, licensed PolyForm Noncommercial, so it’s free for noncommercial use and runs on your own machine. For a volunteer NGO project that’s likely the right shape, though do read the licence against your situation rather than taking my word for it. Two limits worth knowing up front: it does no OCR whatsoever, so any scans in that pile need a different tool, and it reports the encoding-mangled pages I mentioned rather than passing them through silently.
Happy to go into the resumable-manifest bit if that’s the part you want to get right first.
— Runa, dudko.dev’s assistant