Email automation
E-mailagent bij Leidse uitgeverij: ISBN-triage in 40s
Een Leidse uitgeverij met 21 mensen krijgt 1.180 auteursmails per week. De helft komt in Klopotek terecht, de helft in een Exchange-archief dat niemand vertrouwt. Dit bouwden we.

Tien voor vier op een dinsdag in Leiden
Het is 15:50 op een dinsdag. De hoofdredacteur van een Leidse uitgeverij met 21 mensen heeft nog negen minuten voor het CB Logistics-aanleverwindow om 16:00 dichtgaat. In haar inbox staan 47 ongelezen auteursmails sinds de lunch. Twee daarvan gaan over ISBN's die botsen met toekenningen die al in Klopotek staan, het titelbeheersysteem dat de uitgeverij sinds 2014 draait. Welke twee, weet ze niet.
Mist ze het window, dan schuiven drie titels een week op in de Nederlandse boekenketen. Drie lanceerdata verschuiven. Een campagne die buiten al loopt, klopt opeens niet meer.
Vóór we de e-mailagent bouwden, was dit een normale dinsdag.
Hoe 1.180 mails per week eruitzien
De redactie verwerkt zo'n 1.180 auteursmails per week. Ruwweg vierhonderd gaan over metadata: ISBN-vragen, aanvragen voor BISAC- en Thema-codes, aanpassingen in flapteksten, wisselingen van medewerkers. Driehonderd zijn manuscriptconcepten en revisies, als Word-documenten en InDesign IDML-pakketten bijgevoegd. Tweehonderd zijn contract- en royaltyvragen die naar een apart team moeten. De rest is alles wat ertussen valt: vakantieafwezigheidsberichten, felicitaties met het nieuwe huis, het hoofdstuk dat de auteur gisteren al stuurde en "voor de zekerheid" graag nog eens stuurt.
Van die 1.180 bevatten er tussen de twaalf en vijfentwintig een ISBN-toekenningsconflict. Een auteur heeft van een externe partij (een co-uitgever, een distributeur, een vorige redacteur) een nummer gekregen dat niet overeenkomt met wat in Klopotek staat. Elk zo'n conflict dat vóór 16:00 niet wordt opgemerkt, kan later die week een logistiek probleem worden bij CB.
Twee systemen die niet met elkaar praten
Klopotek draait on-prem op een Windows Server-cluster waar sinds 2014 niets fundamenteels meer aan veranderd is. Het systeem biedt een SOAP API die in 2009 state-of-the-art was, en een recentere REST-gateway die ongeveer 60% van het SOAP-oppervlak afdekt. Het redactiearchief, dertien jaar auteurscorrespondentie tot terug in 2013, staat op Exchange 2016, on-prem, achter een hardened reverse proxy.
Geen van beide systemen gaat ergens heen. De CFO van de uitgeverij heeft een Klopotek-migratie in vijf jaar twee keer doorgerekend, en in beide gevallen overleefde de spreadsheet het contact met de werkelijkheid niet. Dus wat we ook zouden bouwen, het moest uit allebei lezen, in geen van beide schrijven (in eerste instantie), en de Outlook-clients op de bureaus mochten er nooit door blokkeren.
We stelden niet voor om iets te vervangen. We stelden een sidecar voor.
De sidecar
De agent haalt mail op via een dedicated EWS-impersonation-account dat leesrechten heeft op de vier gedeelde redactie-mailboxen. Elke 25 seconden vraagt hij Exchange om berichten sinds de laatste cursor, via de standaard FindItems-EWS-call op de inbox-folder. We kozen EWS boven Microsoft Graph om een saaie reden: Exchange 2016 on-prem spreekt geen Graph, en het netwerkteam van de uitgeverij had vier maanden besteed aan het hardenen van het EWS-endpoint. We gingen ze niet vragen om dat werk weer ongedaan te maken.
Elk nieuw bericht gaat door een pipeline van drie stappen: extraheren, classificeren, routeren. Extractie is deterministisch: headers parsen, body decoderen, de handtekening strippen met een regex-library die getraind is op de eigen footerpatronen van de uitgeverij. Classificatie is de agent zelf: een klein model met tooltoegang tot Klopotek (alleen-lezen) en tot een vector index van de laatste 18 maanden correspondentie. Routering is weer deterministisch, omdat routering nu juist de plek is waar je geen model creatief wilt hebben.
De hele pipeline draait binnen veertig seconden, van binnenkomst in EWS tot plaatsing in de queue. Het grootste deel van die seconden is EWS-latency.
De ISBN-conflictroute
De interessante route is die voor ISBN's. Als de classifier een waarschijnlijk toekenningsconflict markeert, gebeuren er drie dingen tegelijk.
Eerst extraheert de agent elke ISBN-13-kandidaat uit de body en de bijlagen. ISBN's zijn makkelijk te vinden en makkelijk te missen: dertien cijfers die beginnen met 978 of 979, met een Mod-10-checksum aan het eind. Die checksum-verificatie hebben we zelf geschreven, in plaats van het model te vragen, want het model hallucineert vrolijk een geldige checksum op een string die helemaal niet in de mail voorkomt.
Daarna queryt hij de REST-gateway van Klopotek voor elke gevonden ISBN en legt de huidige toekenningsstatus vast: assigned, reserved, free, of stuck — een Klopotek-status die we als vierde categorie zijn gaan behandelen, omdat hij meestal betekent dat iemands handmatige workflow halverwege is blijven steken.
Vervolgens doet hij een semantische match tegen de vector index van het redactiearchief, om te zien of hetzelfde conflict de afgelopen 90 dagen al besproken is. Zo ja, dan hangt de agent de eerdere thread aan de queue-entry, zodat de redacteur niet vanaf nul begint.
Als één van de gevonden ISBN's in een status zit die niet strookt met wat de mail beweert, belandt het bericht binnen veertig seconden na binnenkomst in EWS in de queue van de hoofdredacteur. De rest gaat naar de normale redactie-inboxen met een voorgesteld label.
GET /api/titles?isbn=9789403621847
Authorization: Bearer …
200 OK
{
"isbn": "9789403621847",
"title": "De stille kant van de stad",
"status": "assigned",
"assigned_to_project": "P-2025-0411",
"last_modified": "2026-06-19T11:22:14+02:00"
}Waarom veertig seconden, en wat dat kost
Veertig seconden is geen magie. Het is een budget. EWS push-notificaties zouden ons sub-seconde latency kunnen geven, maar alleen op een netwerk dat we niet volledig in de hand hebben, en ze breken op manieren die dagen kosten om te debuggen. Elke 25 seconden pollen plus een verwerkingsbudget van 15 seconden gaf ons een worst case van veertig seconden, een best case van tien, en een failure mode (een gemiste poll) die luid en zichtbaar is in plaats van stil.
De prijs van die veertig seconden is dat de queue zich rond het 16:00-window af en toe kan opstapelen. Dat hebben we opgevangen door een tweede poller toe te voegen die tussen 15:30 en 16:05 op een cadans van vijf seconden draait, en de rest van de dag terugschakelt naar 25 seconden. Niet elegant. Werkt wel.
Een SLA van veertig seconden is een budget, geen grens. Kies het traagste nummer dat je nog acceptabel vindt en ontwerp daarvandaan terug, zodat je failure modes luid worden in plaats van stil.
De Klopotek-cache
Het moeilijkste stuk om te bouwen was niet de classifier. Het was de cachelaag tussen de REST-gateway van Klopotek en de agent. Die gateway dekt ongeveer 60% van het Klopotek-datamodel af en knijpt alles boven de honderd reads per minuut per client af. Bij 1.180 mails per week, met twee tot vier lookups per mail, hadden we de gateway dinsdag rond lunchtijd al uitgehongerd.
We bouwden een dunne Postgres cache die titel-, ISBN-, medewerker- en toekenningsdata spiegelt, ververst in vensters van 90 seconden via de REST-endpoints die we vertrouwen, en via directe alleen-lezen SOAP-calls voor de 40% die de REST-gateway niet dekt. De cache bevat zo'n 240.000 rijen en blijft onder de 600MB op disk. De agent leest eruit. De Klopotek-zoek-bookmarklet van de redacteuren leest er óók uit, wat ze niet hadden gevraagd maar stilzwijgend niet meer hebben verwijderd.
We draaiden twee weken lang gateway-direct en cache-backed reads naast elkaar voor we de agent op de cache overzetten. De gateway is nu de writer, de cache is de reader. De lookup-tijd op een titel zakte van "een paar seconden, soms langer" naar onder de 200ms, en dat verschil viel de redacteuren als eerste op. Dat de classifier op de achtergrond het juiste deed, was de minder zichtbare winst.
Wat als eerste brak
Drie dingen braken in de eerste zes weken, en het zijn precies de drie dingen die altijd breken.
Het eerste was authenticatie. Het EWS-impersonation-account had een wachtwoordrotatie-beleid van 90 dagen; dat had niemand ons verteld. Op dag 91 stopte de agent met mail lezen, de redactie merkte het twee uur lang niet omdat Outlook gewoon bleef werken, en wij kregen het belletje om 14:30 op een vrijdag. We zijn overgestapt op een service-account-uitzondering met een gedocumenteerde kwartaalreview en een agenda-herinnering die op naam staat van twee mensen.
Het tweede waren de bijlagen. Auteurs sturen InDesign-bestanden van 80MB. De classifier had ze niet nodig, maar onze extractor haalde ze toch binnen, twee keer, omdat de streaming-API van EWS een re-fetch-patroon kent dat we uit een sample uit 2017 hadden overgenomen. We zijn alles boven de 25MB op de extractie-stap gaan weigeren en doen daarna opnieuw een metadata-only read.
Het derde was dat het model te zelfverzekerd was. In één vroeg geval had een co-uitgever "ISBN 978-94-036-2184-7" in een thread geschreven, maar het werkelijke conflict ging over een andere ISBN verderop in het bericht, in een geciteerd antwoord. De classifier markeerde de verkeerde, de hoofdredacteur vertrouwde die markering, en een titel ging twee weken lang met de verkeerde cover de markt op voordat iemand het opmerkte.
We hebben twee dingen veranderd. De agent hangt nu elke gevonden ISBN aan, in volgorde, met de positie in de thread. En hij weigert een vertrouwensoordeel uit te spreken zodra er meer dan één kandidaat is — de queue-entry zegt simpelweg "twee ISBN's, graag controleren", en de redacteur beslist. Hetzelfde principe wordt algemener uitgewerkt in Anthropic's Building Effective Agents: kies eenvoudige, composeerbare patronen boven end-to-end-autonomie. Daar zijn we het mee eens, en dat heeft ons geld gekost.
Waar mensen de knoop blijven doorhakken
De agent schrijft niet naar Klopotek. Hij antwoordt geen auteurs. Hij verplaatst geen berichten tussen folders. Alles wat hij oplevert is een queue-entry, met een link terug naar het originele bericht in Outlook en een gestructureerde samenvatting die de redacteur desgewenst in Klopotek kan plakken.
Dat is deels een vertrouwensbeslissing en deels een juridische. Auteurscorrespondentie bij een Nederlandse uitgever omvat contractonderhandelingen, en dat betekent grondslagdiscussies onder AVG-artikel 6 die we liever niet wilden voeren. Door elke uitgaande actie door een mens te laten starten, hielden we de agent in een categorie die het juridische team begreep: een zoektool die voorsorteert.
Het bleek ook de juiste productkeuze. De redacteuren vertrouwden de queue zodra die ophield slim te willen zijn. Auto-replies hebben ze nooit vertrouwd, en dat gingen ze ook nooit doen.
Wat het veranderde
Een halfjaar verder rapporteert de uitgeverij twee getallen die ertoe doen. De queue van de hoofdredacteur telt gemiddeld 18 items per dag, en de mediane tijd-tot-beslissing op die items is zes minuten. Het aantal titels dat het 16:00-window van CB miste door een metadata-issue ging van "ongeveer één per twee weken" naar "twee in een halfjaar, allebei door ons veroorzaakt". Het auteursvolume is nog steeds 1.180 mails per week. Het is alleen niet meer de bottleneck.
De uitgeverij heeft ons niet gevraagd om schrijfpaden toe te voegen. Wij hebben er ook niet op aangedrongen. De queue is genoeg, en "genoeg" is een prima eindbestemming.
Toen we de e-mailagent voor deze Leidse uitgeverij bouwden, was het gat tussen de REST-gateway van Klopotek en wat de redactie écht moest weten waar we steeds tegenaan liepen. We hebben het opgelost met de dunne cachelaag hierboven, waar zowel de agent als de menselijke redacteuren uit lezen — precies het soort AI-agent-werk dat tussen een oud system of record en de mensen die ermee leven in zit, en dat is in 2026 het meeste van wat we leveren.
Wil je morgen de kleinst mogelijke versie hiervan proberen: open je CRM of titelsysteem, tel de inkomende mails per week die ermee in aanraking moeten komen, en klok hoe lang een gemiddelde lookup duurt. Is dat product groter dan wat je team kan hebben, dan heb je hetzelfde probleem als deze uitgeverij.
Kern
Een SLA van veertig seconden is een budget, geen grens. Kies het traagste nummer dat je nog acceptabel vindt en ontwerp daarvandaan terug, zodat je fouten luid worden, niet stil.
FAQ
Hoe leest de agent mail uit Exchange 2016 on-prem?
Via een dedicated EWS-impersonation-account met leesrechten op de vier gedeelde redactie-mailboxen. Hij polt elke 25 seconden, en elke 5 seconden in de 30 minuten vóór het CB Logistics-window dichtgaat.
Waarom een SLA van 40 seconden in plaats van real-time?
EWS push-notificaties breken op afgeschermde netwerken op manieren die dagen kosten om te debuggen. Pollen op 25 seconden plus een verwerkingsbudget van 15 seconden geeft een luide failure mode en een voorspelbare belasting op Exchange.
Schrijft de agent terug naar Klopotek?
Nee. Elke actie die hij oplevert is een queue-entry met een gestructureerde samenvatting die een redacteur in Klopotek kan plakken. We hebben schrijfacties bewust door mensen laten starten, om AVG-redenen en omdat de redactie het systeem meer vertrouwde zodra het ophield slim te willen zijn.
Wat was het moeilijkste stuk om te bouwen?
Niet de classifier. De Postgres-cache tussen de REST-gateway van Klopotek en de agent. De API van Klopotek dekt ongeveer 60% van zijn datamodel af en knijpt alles wat zwaarder is af, dus de cache werd de facto de zoekinterface voor de catalogus.