← Blog

Email automation

Email agent voor btw-reminders: een accountancy-playbook

Het is maandagochtend 7:42 als een fiscalist een wachtrij opent met 240 concept btw-reminders. Elk 's nachts geschreven door een agent. Elk wachtend op een klik.

Jacob Molkenboer· Oprichter · A Brand New Company· 15 jun 2026· 9 min
Crème envelop met limegroen lint op donkergroen vloeiblad, koperen paperclip en gevouwen bon, ivoorkleurig papier.

Het is 7:42 op een maandagochtend in Arnhem. Een fiscalist genaamd Eline opent haar wachtrij: 240 concept e-mails, elk een btw-aangifte reminder voor een andere klant, elk 's nachts geschreven door een agent. Ze scant de eerste vijf, keurt ze in een batch goed en begint aan de volgende.

Die wachtrij was vroeger een spreadsheet, daarna een reeks mail merges, en uiteindelijk een junior medewerker die elke maandag van 06:00 tot 09:00 doorploeterde. Vandaag is het software, en het kantoor stuurt 1.840 reminder-threads per week zonder dat een fiscalist er nog eentje vanaf nul schrijft.

Dit is de playbook van hoe we daar zijn gekomen. Het kantoor draait Twinfield voor de boekhouding en Visionplanner voor advies-dashboards, een 19 jaar oude stack die niet standaard met een email agent komt. De beperkingen waren scherp. Geen door de klant beschermde data mocht ongecontroleerd het pand verlaten. Geen reply met het woord suppletie of naheffing mocht automatisch beantwoord worden. Elke uitgaande e-mail moest binnen drie seconden te beoordelen zijn in de inbox van een fiscalist.

Dit is wat we hebben gebouwd, waar we de mist in gingen, en wat je eruit kunt meenemen als je op een vergelijkbare stack zit.

De stack waar we in stapten

Twinfield is al twee decennia de ruggengraat van Nederlandse mkb-boekhoudingen. Het biedt een SOAP-tijdperk API, gedocumenteerd op het Wolters Kluwer developer portaal, en een nieuwer REST-oppervlak dat ongeveer 60% van de SOAP-endpoints dekt. Visionplanner zit als rapportage- en advieslaag bovenop Twinfield.

Het kantoor had drie problemen.

Ten eerste: btw-aangifte reminders gingen de deur uit als een gebatchte mail merge vanuit een Word-template uit 2014, zonder klantspecifieke risicotaal. Iedere klant kreeg dezelfde standaardtekst. De fiscalisten ontvingen daardoor zo'n 80 verwarde replies per week ("maar mijn Q4 was toch een teruggave?") die vier uur aan partner-tijd opslokten.

Ten tweede: het kantoor was naar 33 mensen gegroeid, maar de partners beoordeelden zelf nog elke reminder-thread voor klanten met een kwartaalomzet boven 250k EUR. Die review-stap was de bottleneck, niet het schrijven.

Ten derde: de bestaande Twinfield-sync was een Python-script op een Windows VM dat twee keer per maand omviel en door IT herstart moest worden.

We werden niet ingehuurd om het boekhoudsysteem te vervangen. We werden ingehuurd om de reminder-pijplijn van het bord van de partners te halen, zonder software te introduceren die ooit een verkeerd antwoord aan een belastingvraag zou kunnen sturen.

Architectuur in één diagram

De vorm van wat we bouwden:

Twinfield (SOAP)  ->  sync worker (Node)  ->  Postgres (client + period state)
                                                       |
                                                       v
                                              draft generator
                                                       |
                                                       v
                            Cloudflare Worker (auth + redaction + routing)
                                                       |
                                                       v
                                               LLM (drafting only)
                                                       |
                                                       v
                                              fiscalist queue
                                                       |
                                                       v
                                              Microsoft 365 send

Twee ontwerpbeslissingen zijn het belangrijkst.

Het model raakt Twinfield of Visionplanner nooit rechtstreeks aan. Het ontvangt een vooraf geredigeerde JSON-payload van een Cloudflare Worker proxy, die KvK-nummers, bankrekeningen en volledige namen eruit haalt voordat een request de Cloudflare-tenant van het kantoor verlaat. Die proxy is een bewuste knijpfles. Hij logt elke uitgaande token, dwingt een model-allowlist af, en weigert payloads boven 4kb (wat alleen gebeurt als de upstream draft generator een bug heeft). Als je de recente discussie over beschikbaarheid van modelproviders en procurement-risico hebt gevolgd: dit is de laag waarmee het kantoor in een middag van provider kan wisselen zonder een regel applicatiecode aan te raken.

De fiscalist-queue is het enige systeem dat mag verzenden. De drafting-laag kan geen bericht op de SMTP-draad zetten. Zelfs als het model een perfect geformuleerde e-mail produceert, landt die in een Postgres-rij met status = 'queued_for_review', en moet een mens klikken voordat Microsoft 365 hem ziet.

Kernpunt

Voor elke accountancy email agent geldt: de agent mag nooit het system of record zijn voor "mag dit verstuurd worden?". Die rol hoort bij een mens, een queue en een database-constraint.

De reminder-loop

Elke zondag om 22:00 Amsterdamse tijd haalt de sync worker vier dingen uit Twinfield voor elke klant op de roster van het kantoor:

  1. Het huidige btw-tijdvak en de aangiftedeadline.
  2. De aangiftestatus (concept, ingediend, voldaan).
  3. Het voorlopige bedrag te betalen of terug te ontvangen.
  4. Het aangiftegedrag van de laatste drie kwartalen (op tijd, te laat, suppletie ingediend).

Die vier signalen gaan in een Postgres-rij. De draft generator start om 23:00, loopt door de rijen waar binnen zeven dagen een reminder verschuldigd is, en bouwt per klant een payload:

{
  "client_handle": "c_18443",
  "deadline": "2026-06-30",
  "period_label": "Q2 2026",
  "amount_band": "10k_50k_refund",
  "history": {
    "last_three": ["on_time", "on_time", "late_3d"],
    "ever_suppletie": false
  },
  "tone": "neutral_professional",
  "language": "nl"
}

Let op de amount_band. We sturen het echte EUR-bedrag niet naar het model. Het model krijgt een bandbreedte, en de uiteindelijke e-mailtemplate vult bij het renderen het exacte bedrag uit Postgres in. Als het model een bedrag hallucineert, gooit de templating-laag een fout omdat het geproduceerde bedrag niet matcht met het band-token uit de prompt. Die ene truc heeft in productie al twee upstream bugs gevangen, allebei van ons.

Het model retourneert Nederlandse prosé. We renderen die door een Mustache-template die de echte bedragen, de echte deadline en de echte aanhef injecteert, en schrijven dan de rij naar de fiscalist-queue.

De suppletie- en naheffing-parkeerstrook

Dit is het stuk dat het langst kostte om goed te krijgen.

Een suppletie is een aanvullende btw-aangifte die een ondernemer indient als er een fout in een eerder ingediende aangifte wordt ontdekt. Een naheffing is een aanvullende aanslag van de Belastingdienst, meestal met rente en soms met een boete. Beide woorden zijn reply-killers. Op het moment dat een klant een van beide woorden gebruikt, is het gesprek van "reminder" verschoven naar "hier is een fiscalist nodig".

We bouwden een reply-classifier die twee dingen doet:

const TRIPWIRE_TERMS = [
  'suppletie', 'suppleties',
  'naheffing', 'naheffingsaanslag',
  'boete', 'rente',
  'bezwaar', 'bezwaarschrift',
  'controle', 'boekenonderzoek',
];

function shouldPark(replyBody) {
  const haystack = replyBody.toLowerCase();
  return TRIPWIRE_TERMS.some(term => haystack.includes(term));
}

Als shouldPark true retourneert, wordt de reply naar een fiscalist-queue gerouteerd met prioriteit high, krijgt de klant 30 dagen lang de markering in_human_conversation = true, en stopt de agent met het opstellen van verdere reminders voor die klant tot een partner de vlag opheft.

De classifier is bewust dom. We probeerden eerst een model-gebaseerde intent classifier. Die was 96% accuraat. 96% accuraat is niet goed genoeg wanneer die 4% een klant bevat die schrijft "ik heb een naheffing gekregen, wat nu?" en daarop een vriendelijke autoreply over de deadline van volgend kwartaal terugkrijgt. Een regex over twaalf Nederlandse woorden geeft 100% recall op de dingen die ertoe doen, en de false positives (een klant die schrijft "geen naheffing dit kwartaal, gelukkig") betekenen alleen dat een fiscalist één extra e-mail per week leest.

Let op

Als je een email agent bouwt voor een gereguleerde workflow, moet je veiligheidslaag dom genoeg zijn dat een partner hem in 30 seconden kan auditen. Een model-classifier die je niet kunt beredeneren is geen veiligheidslaag.

Twinfield- en Visionplanner-valkuilen

Drie dingen beten ons.

Twinfield rate-limit op kantoor-niveau, niet op gebruikersniveau. Het kantoor heeft 33 medewerkers en één kantoor. Onze sync worker parallel laten draaien met hun bestaande Windows VM-script betekende dat we van elkaars quotum aten. We zetten beide jobs achter één token bucket in onze Node worker en brachten throttle-errors van zo'n 200 per week terug naar nul.

Visionplanner geeft advies-snapshots terug, geen transactionele waarheid. Voor een reminder wil je de laatst ingediende aangifte, niet het laatst door een analist bijgewerkte cijfer. Dat leerden we de harde manier toen een fiscalist een reminder markeerde die een bedrag citeerde uit het Visionplanner-dashboard, dat drie dagen eerder door een analist was overschreven. We lezen bedragen nu alleen nog uit Twinfields vatReturn endpoint en gebruiken Visionplanner uitsluitend voor narratieve context waar de reminder niet numeriek van afhankelijk is.

Twinfield session-tokens verlopen na 60 minuten. De SOAP-foutcode voor een verlopen token is dezelfde als die voor "client niet gevonden". We ontdekten dat toen de sync worker op een zondag stilletjes 11 klanten oversloeg. We behandelen nu elke Authentication failed response als trigger voor een token-refresh en proberen het één keer opnieuw voordat we de klant als ontbrekend loggen.

Wat de fiscalist daadwerkelijk ziet

De wachtrij leeft in een kleine interne webapp. Drie kolommen: onderwerpregel, klant, verzendmoment. Eén paneel: de volledige draft. Twee knoppen: goedkeuren, bewerken. Een toetscombinatie voor goedkeuren. Eline werkt op een goede maandag 240 drafts weg in 22 minuten, sneller dan de Word merge ooit was, omdat ze taal beoordeelt die al is afgestemd op de specifieke klant in plaats van vanaf nul te schrijven.

Goedkeuringen stromen terug naar de sync worker, die verzendt via de Microsoft 365 tenant van het kantoor. Bounces, replies en out-of-office reacties komen terug via Microsoft Graph, krijgen de regex-tripwire eroverheen, en worden ofwel naar een fiscalist gerouteerd ofwel als no_action gelogd.

Het kantoor verstuurt nu 1.840 reminder-threads per week. De partners beoordelen klant-e-mails niet meer standaard. De vier uur per week verwarde-reply-triage is gedaald naar ongeveer 35 minuten, vrijwel allemaal suppletie- en naheffingsgesprekken die sowieso bij een fiscalist hadden gemoeten.

Wat je vandaag kunt doen

Als jij een accountancy-mailpijplijn beheert: doe dit vóór de lunch. Schrijf de vijf Nederlandse woorden op die, als een klant ze gebruikt, je automatisering pardoes moeten stoppen. Stop ze in een regex. Verbind die met een Slack-kanaal waar één partner naar kijkt. Je hebt nu het goedkoopste, domste, best te auditen vangnet dat een email agent kan hebben, en de agent zelf bouw je later.

Toen we dit bouwden voor het Arnhemse kantoor was het modellaag-stuk niet het lastigst. Het lastigst was de partners overtuigen dat een regex van zes regels een verdedigbaarder compliance-grens is dan welk model dan ook kan zijn. Dat is het werk dat we bij de meeste email automation-opdrachten uiteindelijk doen: de veiligheidslaag saai genoeg maken dat een toezichthouder hem in een koffiepauze kan lezen.

Kern

In een gereguleerde mailworkflow moet de veiligheidslaag dom genoeg zijn dat een partner hem in 30 seconden kan auditen. Een model-classifier die je niet kunt beredeneren is geen veiligheidslaag.

FAQ

Waarom geen model-classifier voor de suppletie-tripwire?

Omdat 96% recall onacceptabel is wanneer die 4% mis een klant betreft die over een naheffingsaanslag schrijft en een vrolijke reminder terugkrijgt. Een regex over twaalf woorden geeft 100% recall op de termen die ertoe doen, en een partner kan hem in seconden auditen.

Kan de agent zelf de btw-aangifte indienen?

Nee, en dat was een bewuste beperking. De agent stelt reminder-communicatie op. Het indienen van de aangifte blijft binnen Twinfield met de handtekening van een fiscalist. Drafting en indienen in één systeem mengen is de verkeerde plek om het regulatoire risico te nemen.

Wat gebeurt er als Twinfield zondagavond onbereikbaar is?

De sync worker probeert het 90 minuten lang opnieuw met exponentiële backoff en alerteert daarna on-call. De draft generator slaat zijn run van 23:00 over. Reminders gaan in het slechtste geval een dag later de deur uit, wat prima is omdat deadlines weken weg zijn, geen uren.

Waarom alle model-calls via een Cloudflare Worker routeren?

Eén knijpfles voor redactie, logging, model-allowlist en provider-routing. Bij een storing of procurement-issue bij een provider verandert het kantoor één environment variable in plaats van applicatiecode aan te raken, en is elke uitgaande token al gelogd voor compliance.

Hoe groot moet een accountancy zijn voordat dit zich terugverdient?

Rond de 15 fee-earners en 400 actieve klanten is de bodem waar de rekensom begint te kloppen. Daaronder dekken de bespaarde partner-uren per week de engineering en de operationele discipline die de pijplijn vraagt nog niet.

email automationai agentsautomationworkflowcase studyintegrations

Iets bouwen?

Start een project