Email automation
Email agents in de boekhouding: een Twinfield-filer in 47 seconden
Een Haarlems accountantskantoor stond elke ochtend een Make.com-scenario van negen stappen te babysitten. Wij bouwden het om tot één email agent. Boekingstijd per factuur: 47 seconden.

Het eerste telefoontje kwam dinsdagochtend om 07:42 binnen. De officemanager van een Haarlems accountantskantoor met 23 medewerkers staarde naar een rode fout op stap zeven van een Make.com-scenario dat ze niet zelf had gebouwd en niet helemaal begreep. Het scenario moest bonnetjes uit een gedeelde inbox lezen, ze door OCR halen, de leverancier classificeren, die opzoeken in Twinfield, en een inkoopfactuur boeken op de juiste grootboekrekening. In plaats daarvan was het vastgelopen op een PDF van een Belgisch tankstation, waar de btw-regel op een plek stond die de regex niet had voorzien. Negentien facturen stonden erachter in de wachtrij. Haar boekhouder zat in de trein.
Dit is het verhaal van waarom we dat scenario eruit hebben gesloopt en vervangen door één email agent, en wat we hebben geleerd over de naden tussen een LLM, een 25 jaar oud boekhoudproduct en een team dat de maand op tijd moet afsluiten.
Het oude scenario, eerlijk beschreven
Het kantoor had de Make.com-flow geërfd van een freelancer die inmiddels weg was. Het waren negen stappen. De exacte lijst, zoals die op het canvas stond:
- Gmail-watcher op
facturen@. - Filter op type bijlage (PDF, JPG, HEIC).
- Upload naar Google Drive, in een map per datum.
- OCR via een externe module.
- Regex-extractor die leveranciersnaam, IBAN, factuurnummer, totaalbedrag en btw eruit haalde.
- Lookup tegen een Google Sheet die leveranciers koppelde aan Twinfield-grootboekcodes.
- Branch: bekende leverancier vs. nieuwe leverancier.
- Twinfield SOAP-call om een inkoopboeking aan te maken.
- Reply op de originele mail met een bevestiging, of escaleren naar de boekhouder als er een veld leeg was.
Op een rustige dag werkte het. Op een drukke dag brak de regex op stap 5 op alles wat ongebruikelijk was: een bonnetje met de telefoon gefotografeerd, een Nederlandse factuur met een buitenlands btw-formaat, of een bijlage die in werkelijkheid een betalingsherinnering was in plaats van een factuur. De escalatie ging zo vaak af dat de boekhouder de gewoonte had ontwikkeld om als eerste 's ochtends Make.com te openen en de wachtrij met de hand leeg te halen. Die gewoonte was de echte kostenpost. 35 tot 50 minuten per dag, elke dag, voor iemand die voor €95 per uur factureert.
Wat een agent verandert aan dit probleem
Een regex matcht of matcht niet. Een LLM kan een bonnetje lezen zoals een mens dat doet: hij ziet dat het vetgedrukte getal bovenaan het factuurnummer is, dat de regel met BTW 21% de btw is, dat het kleine logo onderaan de leverancier aanduidt, zelfs als de handelsnaam op de bankregel anders is. Hij ziet ook wanneer een bijlage helemaal geen factuur is, iets wat het oude scenario niet kon.
Wat een agent niet verandert is het saaie deel: je hebt nog steeds een deterministische manier nodig om naar Twinfield te schrijven, je hebt nog steeds een plek nodig om bestanden te bewaren die de accountants van het kantoor later kunnen inzien, en je hebt nog steeds een audit log nodig dat een mens in gewone taal kan lezen. Die delen hebben we bewust saai gehouden.
De winst zat niet in een workflow-tool vervangen door een LLM. De winst zat in negen brakke lijmstappen vervangen door één leesstap, en de schrijfstap deterministisch laten.
De nieuwe opzet
Eén inbox, één agent-proces, drie tools die de agent mag aanroepen. De agent leest een binnenkomende mail, haalt de factuur eruit, beslist wat er moet gebeuren, en boekt hem ofwel meteen of stelt een specifieke vraag aan de boekhouder. Geen branches op een canvas, geen regex.
Het tool-oppervlak dat we aan het model hebben gegeven is klein. Kleine tool-oppervlakken zijn de manier om een agent eerlijk te houden.
// tools.ts — the only things the agent can do
export const tools = [
{
name: "lookup_supplier",
description: "Find a supplier in Twinfield by name, IBAN, or VAT number. Returns ledger code and default cost account, or null.",
input_schema: {
type: "object",
properties: {
name: { type: "string" },
iban: { type: "string" },
vat_number: { type: "string" },
},
},
},
{
name: "file_purchase_invoice",
description: "Create a purchase transaction in Twinfield. Idempotent on (supplier_code, invoice_number).",
input_schema: {
type: "object",
required: ["supplier_code", "invoice_number", "invoice_date", "total_incl_vat", "vat_lines", "cost_account"],
properties: {
supplier_code: { type: "string" },
invoice_number: { type: "string" },
invoice_date: { type: "string", format: "date" },
total_incl_vat: { type: "number" },
vat_lines: {
type: "array",
items: {
type: "object",
properties: {
rate: { type: "number" },
base: { type: "number" },
vat: { type: "number" },
},
},
},
cost_account: { type: "string" },
attachment_drive_id: { type: "string" },
},
},
},
{
name: "ask_bookkeeper",
description: "Reply to the original email with a specific question. Use only when a field cannot be determined and is required.",
input_schema: {
type: "object",
required: ["question", "fields_in_doubt"],
properties: {
question: { type: "string" },
fields_in_doubt: { type: "array", items: { type: "string" } },
},
},
},
] as const;
Drie tools. Dat is de hele API die het model krijgt. Al het andere (Drive-upload, PDF-parsing, Twinfield SOAP-envelope, audit log) draait in code rondom de agent. Het model beslist, de code handelt.
Het Twinfield-stuk waar niemand je voor waarschuwt
De publieke web services van Twinfield zijn SOAP. En dat zijn ze al heel lang. Authenticatie loopt via OAuth 2.0, prima, maar de transactiecalls zelf praten XML en zijn kieskeurig over whitespace, namespace-prefixes, en de exacte vorm van het <office>-element. Het LLM hebben we daar nergens bij gelaten. De tool file_purchase_invoice is een getypeerde functie die de XML opbouwt uit gevalideerde velden. Als de XML fout is, dan komt dat doordat wij hem fout hebben geschreven, niet doordat het model een tag heeft gehallucineerd.
Idempotentie bleek belangrijker dan we verwachtten. Dezelfde factuur kan twee keer in de inbox belanden: één keer doorgestuurd door de leverancier, één keer door de officemanager die niet zeker wist of de leverancier hem al had verstuurd. De tool sleutelt op (supplier_code, invoice_number) en geeft bij een duplicaat het bestaande transactie-id terug in plaats van een tweede aan te maken. Die ene beslissing haalde een hele categorie 'waarom staat deze factuur twee keer geboekt'-vragen uit de week van de boekhouder.
Als je een agent op Twinfield aansluit zonder idempotentie, boek je dubbel. De SOAP-API neemt het netjes aan. Je klant haalt het er niet netjes weer uit.
Wat 47 seconden eigenlijk betekent
Het getal in de titel is de mediane wandkloktijd van mailontvangst tot geboekte transactie, gemeten over vier weken productieverkeer. Ruwweg: zo'n 9 seconden voor het ophalen van de bijlage en de PDF-naar-image-conversie, zo'n 22 seconden voor het model om de factuur te lezen en lookup_supplier aan te roepen, zo'n 11 seconden voor de SOAP-round-trip met Twinfield, en zo'n 5 seconden voor de bevestigingsreply en de Drive-archivering. Het model is niet het trage deel. Twinfield is het trage deel, en dat is het sinds 2003.
Het getal dat er voor het kantoor echt toe doet is een ander. De boekhouder was 35 tot 50 minuten per dag bezig met de wachtrij. Nu doet ze er ongeveer 6 minuten per dag over om vanaf haar telefoon, onderweg naar werk, specifieke ask_bookkeeper-vragen te beantwoorden. De meeste van die vragen gaan over een ontbrekende kostenpost bij een gloednieuwe leverancier, en dat is een keuze die een mens hoort te maken.
Waar de agent nog om hulp vraagt
We hebben niet geprobeerd om de escalatiegraad op nul te krijgen. We hebben geprobeerd om elke escalatie de aandacht van een mens waard te maken. Drie categorieën zijn overgebleven:
- Nieuwe leveranciers. De eerste factuur van een leverancier die het kantoor nog nooit heeft gezien, vraagt om een grootboekcode en een kostenpost die door een mens worden gekozen. De agent haalt al het andere er al uit en biedt een one-click-reply aan.
- Onduidelijke documenten. Creditnota's, pro-formafacturen en saldo-overzichten lijken op facturen maar zijn het niet. De agent vlagt ze met de specifieke reden erbij.
- Btw die niet klopt. Als de som van de btw-regels niet binnen €0,02 overeenkomt met het gedrukte totaal, weigert de agent te boeken en vraagt hij na. Bijna altijd is het een scanartefact, maar 'bijna altijd' is geen goede reden om stilletjes het verkeerde bedrag te boeken.
Wat we hebben weggegooid uit de Make.com-versie
De Google Sheet met leverancier-naar-grootboek-mapping is weg. Twinfield weet dit al. We hielden een parallelle kopie bij omdat het oude scenario het makkelijker vond om uit Sheets te lezen dan de SOAP-API aan te roepen. De agent roept de SOAP-API aan. Eén bron van waarheid.
De regex-stap is weg, vanzelfsprekend. Net als de branch voor 'bekende vs. nieuwe leverancier' (de agent doet beide paden in dezelfde prompt). En de Drive-structuur met datummappen, die we hebben vervangen door een platte archiefmap gesleuteld op het Twinfield-transactie-id. Accountants vinden dingen sneller als de bestandsnaam overeenkomt met de boeking waar ze naar kijken.
De runtime, in het kort
De agent draait in een klein Node-proces achter de bestaande mailserver van het kantoor. Hij draait niet in een workflow-tool. Eén loop: poll de inbox, geef één bericht tegelijk door aan de agent, schrijf het resultaat naar een append-only audit log op disk. Het audit log is gewoon JSONL en de boekhouder kan er met grep doorheen. Dat klinkt vanzelfsprekend, totdat je een niet-engineer hebt geprobeerd uit te leggen waarom een bepaalde factuur op de verkeerde rekening is geboekt aan de hand van de run history van een no-code-tool.
Het stuk van Anthropic over building effective agents verwoordt de verschuiving goed: het interessante werk in agent-systemen is niet langer de model-call, het is de kleine deterministische loop eromheen. Onze loop is 180 regels TypeScript. Sinds we hem live hebben gezet, is hij twee keer aangepast.
Wat het kostte om te bouwen, en wat het kost om te draaien
De bouw duurde vier weken, waarvan twee zijn opgegaan aan het reverse-engineeren van de SOAP-randgevallen van Twinfield en het schrijven van een testopstelling die echte facturen tegen een sandbox-office afspeelt. Modelkosten zitten op zo'n €0,04 per geboekte factuur tegen het huidige tarief. Het kantoor verwerkt circa 1.400 facturen per maand, dus de modelrekening komt op ongeveer €56. De vrijgespeelde uren van de boekhouder zijn tegen haar uurtarief ruwweg €3.200 per maand waard. De rekensom is niet subtiel.
Afsluitend
Toen we de email agent voor dit kantoor bouwden, was waar we steeds tegenaan liepen Twinfields tolerantie voor misvormde SOAP en nul tolerantie voor duplicaten. Dat hebben we opgelost door de schrijfstap deterministisch en idempotent te houden, en het model alleen het lezen te laten doen. Heb je een vergelijkbaar probleem (een inbox, een legacy systeem van vastlegging, iemand die elke ochtend een wachtrij leegmaakt), dan is dat het patroon waar we zelf mee zouden beginnen. Wij bouwen dit soort email agents voor Europese MKB-bedrijven.
Het kleinste dat je vandaag kunt doen: open je eigen Make.com- of Zapier-dashboard, sorteer scenario's op aantal fouten over de afgelopen 30 dagen, en schrijf de naam op van de persoon die die fouten met de hand oplost. Die persoon is het project.
Kern
Vervang de leeslaag van je finance-automatisering door een agent, houd de schrijflaag deterministisch en idempotent, en de wachtrij heeft geen menselijke babysitter meer nodig.
FAQ
Waarom Make.com niet houden en er gewoon een LLM-stap aan toevoegen?
Geprobeerd. De overdracht tussen Make.com-modules en een LLM-stap liet de regex-extractor en de branch-logica gewoon staan. De hele leeslaag vervangen door één agent was simpeler dan negen stappen oplappen.
Boekt de agent wel eens de verkeerde factuur?
Tot nu toe niet, over vier weken en circa 1.400 facturen. De idempotentiesleutel en de btw-controle vangen de gevallen waarin het mis zou gaan. Bij twijfel escaleert de agent in plaats van te gokken.
Is de SOAP-API van Twinfield lastig om mee te werken?
Oud, niet lastig. OAuth 2.0 voor auth, XML voor transacties, streng over elementvolgorde. Bouw één keer een getypeerde wrapper, test hem tegen een sandbox-office, en je hoeft hem maandenlang niet meer aan te raken.
Kan een niet-technische boekhouder dit zelf onderhouden?
De leveranciersinstellingen onderhoudt ze direct in Twinfield, en dat was ze toch al van plan. De agent-code onderhouden wij. Het audit log is gewoon JSONL waar ze met grep doorheen kan.
Wat gebeurt er als Twinfield eruit ligt?
De agent maakt de leesstap af, zet de file_purchase_invoice-call in de wachtrij en probeert het opnieuw met backoff. De bevestigingsmail gaat pas uit als de SOAP-call gelukt is, zodat de boekhouder nooit een valse bevestiging te zien krijgt.