The Feature Nobody Asked For, Doing All the Work
Konvoi is a fleet management platform. Vehicles, drivers, inspections, and the Belgian speciality: traffic fines, which arrive as PDFs from a dozen authorities in a dozen layouts and have to be matched to whoever was actually driving.
Building it, we noticed the shape of where the effort went. Not the fleet logic. Not the driver matching. The effort went into getting documents into the system at all: the scanner in the workshop that emails to a fixed address, the accountant who forwards a batch of PDFs, the authority that posts a scan, the ZIP file somebody drops in because it was easier.
Then we started a second project and wrote it again. Then a third.
At that point you either accept that you now have three slightly different ingestion pipelines to maintain, or you admit the pipeline was the product all along. We took the second option and pulled it out into Sluice.
The Claim
Look at almost any line-of-business application and there is a document at the front of it. Invoices before accounting. Delivery notes before stock. Fines before fleet. Prescriptions before care. Contracts before anything legal. Quotes, purchase orders, inspection reports, timesheets, customs paperwork.
Before the system there is always a pile of paper, and someone has to get it inside.
The application is the interesting part on the org chart. The intake is where the work actually is, and it is the part that never appears in the estimate. Somebody always thinks it is “just parse the PDF.”
It is not just parse the PDF. It is: the PDF arrives through six different doors, half of them arrive as scans of scans, one door is a printer from 2014, one is a mailbox with four years of history, the same document arrives twice a week, and when it goes wrong someone needs to look at the original - the actual original, not a version something helpfully re-rendered.
One Function, Everything Goes Through It
The architectural decision that makes Sluice work is boring and load bearing. Every intake channel funnels through a single function that takes a buffer.
Whatever the door is - a webhook, a browser upload, a file dropped over SFTP, an email attachment - it ends up calling the same seam. That call writes the raw bytes to immutable storage, hashes them, deduplicates, creates a document row with full provenance, and enqueues processing.
Everything downstream is identical regardless of how the document arrived. Every new channel is a small adapter with no pipeline of its own.
Discipline about that seam is the difference between a platform and six pipelines in a trenchcoat pretending to be a platform.
The Original Bytes Are Sacred
Raw artifacts land at a content-addressed path before anything touches them: workspace, then year, month, day, then the SHA-256 hash and the extension. Never overwritten. Derived things - the OCR’d PDF, thumbnails, extracted JSON, exports - live under separate prefixes.
This rule exists because we broke it. In Konvoi, the OCR step wrote its output over the original. It worked fine, right up until someone needed to see what the authority had actually sent, and what came back was our own rendering of it. Everything looked plausible. Nothing was evidence any more.
Keep the thing that actually arrived. Copies are useful, but only the original settles an argument.
For a document platform, “we still have exactly what you sent us, byte for byte” is not a nice-to-have. It is most of the reason to trust the thing. So it is now a structural property rather than a habit: the raw write happens before processing, and no code path overwrites that key.
Deduplication comes free from the same hash. The same fine forwarded by the accountant, the office manager and the driver is one document with three arrival records.
Six Doors, One Room
The channels currently built:
Webhook. A bearer token per credential, one endpoint, push whatever you have.
Browser upload. Drag files in, including a ZIP, which gets unpacked with limits on entry count and size and no nested archives.
SFTP, FTP and SMTP. Actual servers on their own ports, with their own credentials. This is the unglamorous one and it is the one that closes deals. Multifunction printers do not speak REST. They speak scan-to-folder and scan-to-email, they were configured in 2016, and nobody is replacing them because you built a nice API.
Mail inbound. Signature-verified, multi-domain, routed by recipient address, so a workspace can hand out invoices@ and fines@ as separate intake addresses.
Polled mailboxes. Gmail, Outlook and plain IMAP. One linked account can carry several folder-scoped streams, which matters because the real request is never “ingest my mailbox,” it is “ingest that folder and leave the rest alone.”
Belgian E-box is the next one, because in Belgium official post is increasingly a government inbox rather than an envelope.
Emails Are Not Attachments
One modelling choice earned its keep repeatedly. Every document belongs to an intake transaction rather than floating on its own.
One email is one transaction, carrying subject, body and sender, with the attachments as its documents. One upload batch is one transaction. A ZIP is one transaction. Containers - .eml and .zip - are unpacked and never stored as documents themselves, though the raw container is still backed up immutably.
This gives the classifier context that no parser working from a single PDF can have. A scan with no header and no logo is ambiguous. A scan with no header, attached to a mail with the subject “credit note 2026-0442” from a known supplier, is not. An email with no attachment at all is ingested as a text document so it still flows through classification and review instead of vanishing.
Nothing Is Belgian in the Code
Konvoi’s classification knew about Belgian traffic fines. The types were in the code, the prompts named them, and adding a new one was a deploy.
In Sluice, a workspace defines its own document types: a name, classification hints, and a field schema. Classification prompts are generated from that registry, and extraction runs the model against each type’s schema. There is no built-in taxonomy of any kind.
That is the difference between a fleet product and a platform.
A carpentry business defines delivery notes and supplier invoices. A clinic defines referrals and lab results. Neither one waits for us.
Then everything lands in a review inbox that is deliberately document-type agnostic. The model classifies and extracts, a human confirms or corrects, and the confirmed result goes onward to whichever system asked for it. We are not selling the idea that the model is always right. We are selling the idea that the correction takes eight seconds and happens in one place.
Where the Parsing Services Stop
There is a whole category of good tools that turn an emailed document into structured JSON. We use that pattern ourselves. It stops short of what a business actually needs in four places.
Ingress breadth. Email and an API is not the full set of doors. The printer, the SFTP drop, the shared mailbox with history, the government inbox. The hard channels are exactly the ones an established company already depends on.
Custody. Most parsers keep a document while it is convenient. A business needs the original in 2033 because a dispute is about what arrived, not about what was extracted.
The transaction. Treating each attachment as a standalone file throws away the envelope, which is often the strongest signal in the whole intake.
The last leg. Extraction is halfway. Delivery into the system that consumes it, with retries, provenance and a review trail, is the other half, and it is the half that decides whether anyone stops doing it by hand.
What We Actually Learned
The thing worth taking from this is not that we shipped a product. It is the reason we had one to ship.
The ingestion layer inside Konvoi was general, and it was general because we had been sloppy about coupling exactly once, noticed, and fixed it. Everything funnelled through one seam. The taxonomy was the only Belgian part. Pulling it out was a matter of replacing hardcoded types with a registry and deleting fleet vocabulary.
Most internal components cannot be extracted like that, because their generality was never real. The test is simple and worth applying early: could this run for a customer in a completely different industry if you deleted one file? If yes, you have a product hiding inside your project. If no, you have a project.
Sluice is the one we found.