Email automation
E-mailtriage-agent: hoe we 200 Outlook-regels opruimden
De gedeelde mailbox had 2.431 ongelezen berichten, 47 mappen en een regelset waar niemand aan durfde te komen. Twee sprints later leest één agent elk bericht en routeert het.

Het was 23:18 op een dinsdag in januari toen Lieke, operations lead bij een accountancy met 90 medewerkers boven Eindhoven, stopte met haar pogingen om de Outlook-regels te repareren.
Ze was er twee uur mee bezig geweest. Een junior had de week ervoor een klantmap hernoemd. De regel die afhing van die mapnaam was stilletjes gefaald. Drie dagen aan leveranciersfacturen waren opgehoopt onder een label genaamd 'Diverse'. De gedeelde mailbox toonde 2.431 ongelezen berichten.
De regelset was acht jaar oud. Tweehonderdeenenveertig regels. Mappen die verwezen naar mensen die in 2021 vertrokken waren. Een geneste regel die alles van een specifieke bank naar vier mailboxen kopieerde, waarvan er één gearchiveerd was. Niemand kwam eraan, omdat niemand het end-to-end begreep.
Twee sprints later waren de regels weg. Eén agent leest elk bericht dat binnenkomt op intake@, classificeert het, plaatst het in het juiste Teams-kanaal, stelt een antwoord op en bewaakt de SLA-klok. Dit is wat we deden, op volgorde.
De startsituatie van 200 regels
Outlook-regels zijn een prima feature voor één persoon die vijftig mails per dag verwerkt. Ze vallen om zodra je een gedeelde mailbox hebt, tien mensen die ieder regels kunnen aanmaken, en acht jaar personeelsverloop.
Het kantoor liep tegelijk tegen drie muren aan. De regelset aan de client-kant stootte tegen de 256 KB-quota voor regels in Exchange, waardoor nieuwere regels stilletjes worden uitgeschakeld. De helft van de regels was volgorde-afhankelijk op manieren die niemand had gedocumenteerd. En omdat de mappenstructuur institutionele kennis bevatte ('deze klant gebruikt twee domeinen', 'deze wil zijn aangifte in CC'), was niemand bereid een regel te verwijderen die misschien nog van belang was.
We hebben in week één niets verwijderd. We zetten een parallel-lezing aan.
Twee weken mailbox-verkeer, met de hand gecategoriseerd
Voordat we ook maar één regel routing-code schreven, haalden we veertien dagen mail door de Microsoft Graph API en lazen we elk bericht. Niet de agent. Wij. Samen met Lieke.
De zevenenveertig mappen vielen samen tot dertien echte categorieën:
- Klantintake (nieuwe opdrachtaanvragen)
- Leveranciersfacturen gericht aan het kantoor zelf
- Klantfacturen doorgestuurd ter verwerking
- Jaareinde-stukken
- Salarisvragen
- BTW- en kwartaalaangiftes
- Audit-opvolgingen
- Bankafschriften en geautomatiseerde feeds
- Overheidscorrespondentie (Belastingdienst, KvK)
- Agenda- en vergaderruis
- Interne CC's vanuit de eigen boekhoudsoftware
- Spam en marketing
- Alles waarin curator, faillissement of deurwaarder wordt genoemd, altijd geëscaleerd
De laatste categorie had drie berichten in twee weken. Twee daarvan stonden op dag drie nog ongelezen.
De triage-agent in één alinea
Een Microsoft Graph change-notification subscription hangt op intake@. Elk nieuw bericht vuurt een webhook af naar een kleine, in de EU gehoste service. Die service haalt het volledige bericht op, vraagt een LLM het te classificeren tegen de dertien categorieën hierboven, beslist wat ermee te doen, en schrijft daarna terug naar Graph en plaatst een kaart in Teams. Totaal budget per bericht is onder de twee seconden, waarvan het grootste deel naar de model-call gaat.
De webhook-setup zelf is goed gedocumenteerd en je leest het in een middag door. Het interessante deel is de verlengingsdans: Graph-mailsubscriptions verlopen na ongeveer zeventig uur, en je hebt een job nodig die opnieuw inschrijft voordat ze aflopen.
// renew Graph mail subscription before the 4230-minute ceiling
const renewIfExpiring = async (sub: Subscription) => {
const minutesLeft =
(new Date(sub.expirationDateTime).getTime() - Date.now()) / 60_000;
if (minutesLeft > 60) return;
await graph
.api(`/subscriptions/${sub.id}`)
.patch({
expirationDateTime: new Date(
Date.now() + 4200 * 60 * 1000,
).toISOString(),
});
};
Lees de Microsoft Graph change notifications-gids één keer door, zet een cron, en vergeet het.
De classifier-prompt die uiteindelijk in productie ging
De prompt is bewust onspectaculair. Geen few-shot voorbeelden, geen chain-of-thought-theater. Het model krijgt de vraag om gestructureerde output te leveren, meer niet. Confidence is een getal waarmee de agent bepaalt of hij handelt, geen mening om over te discussiëren.
You are the intake classifier for an accountancy firm.
Read the email below. Output JSON only, with these keys:
category: one of [intake, supplier_invoice, client_invoice,
year_end, payroll, vat, audit, bank_feed, govt,
calendar, internal_cc, spam, escalate]
confidence: number between 0 and 1
escalate: true if the message mentions curator, faillissement,
deurwaarder, dwangbevel, or a named regulator
client_id: CRM id if the sender or domain matches, else null
suggested_reply: a Dutch draft in formal-friendly tone, or null
reasoning: one sentence, max 25 words
Rules:
- If confidence < 0.85, suggested_reply must be null.
- If escalate is true, suggested_reply must be null.
- Never invent a deadline. If no date is present, omit it.
Alles stroomafwaarts baseert zich op die JSON. Er wordt nooit prose-output geparset.
Routing-beslissingen en de human-in-the-loop-lijst
De meeste categorieën gaan automatisch hun route op. Een leveranciersfactuur belandt in het finance-Teams-kanaal met de bijlage uitgepakt en een conceptboeking voor het boekhoudpakket. Een bankafschrift wordt geparset en in de gedeelde schijf gezet. Een brief van de Belastingdienst belandt in het partner-kanaal met de deadline uit de tekst gehaald.
Vier gevallen worden expliciet nooit automatisch afgehandeld:
- Alles wat matcht met de escalatie-trefwoorden hierboven.
- Audit-opvolgingen van externe accountants.
- Elk bericht waar de classifier-confidence onder 0,85 ligt.
- Elke afzender die in het CRM als 'gevoelig' is gemarkeerd (rechtszaak, lopend geschil, recent van adviseur gewisseld).
Die vier gevallen gaan naar een aangewezen partner, met een Teams-ping en de redenering van de classifier eraan vast. De agent stelt geen antwoord voor ze op. Hij doet er zelfs geen suggestie voor.
Concept-antwoorden, nooit automatisch verzenden
De ene beslissing die ons het vertrouwen van het kantoor opleverde, was: de agent stelt op, de mens verzendt. Altijd.
De output van de classifier bevat een conceptantwoord, geschreven in het Nederlands in de huisstijl van het kantoor, met de klantnaam uit het CRM en de relevante deadline ingevoegd. Het concept belandt in de Outlook van de behandelaar als een echte draft, klaar om te versturen. Diegene leest het, past aan indien nodig, klikt op verzenden.
We hebben dit de eerste twee weken gemeten. 71% van de concepten ging ongewijzigd de deur uit. 22% kreeg kleine aanpassingen (een zin erbij, een toonverschuiving). 7% werd herschreven of weggegooid. Dat laatste getal bleef stabiel naarmate het volume groeide, wat ons vertelde dat de agent niet had geleerd om vertrouwen te faken in gevallen die hij had moeten escaleren.
Drie valkuilen die het weten waard zijn
De eerste hadden we zien aankomen. Outlook-regels waren institutioneel geheugen. Voordat we ook maar één van de 241 regels weggooiden, interviewden we de vier mensen die er de meeste hadden aangemaakt, exporteerden we de regelset en vroegen we de LLM om elke regel naar één zin Engels te vertalen. Vervolgens matchten we die zinnen tegen onze dertien-categorieënlijst en markeerden we de zinnen die niet netjes pasten. Dat waren de regels die echte kennis codeerden ('dit domein hoort bij de holding van klant X'). Die kennis duwden we het CRM in, waar hij thuishoorde, voordat we ook maar iets in Outlook aanraakten.
De tweede was BCC-verkeer vanuit het eigen boekhoudpakket. Voor de classifier zag het eruit als echte klantmail, want dat was het in zekere zin ook. We voegden een sender-allowlist toe vóór de classifier draaide, die geautomatiseerde interne mail direct naar een dead-letter-map stuurde.
Out-of-office-replies eten je agent levend op. Een afwezigheidsantwoord van een klant ketst de gedeelde mailbox in, de classifier ziet een Nederlandse zin over 'niet bereikbaar tot maandag', en als je de Auto-Submitted-header niet checkt wordt het als nieuwe intake behandeld. We zagen er drie loops mee in week één. Honoreer altijd de RFC 3834 auto-submitted header voordat de classificatie draait.
Wat er veranderde voor negentig mensen
De cijfers die we bijhielden zijn weinig spectaculair, en daarom vertrouwen we ze.
De gemiddelde tijd van binnenkomst tot eerste menselijke blik daalde van ongeveer zes minuten tijdens kantooruren (en onbeperkt daarbuiten) naar onder de dertig seconden, vierentwintig uur per dag. De 'wie pakt deze?'-berichten op Teams, waarin iemand een doorgestuurde mail plakte met de vraag wie hem oppakte, daalden van een baseline van ongeveer veertig per dag naar onder de vijf. De mappenstructuur van de gedeelde mailbox kromp van zevenenveertig naar negen archiefmappen.
De verandering waar Lieke naar wees, was lastiger te meten. Om 22:00 op een doordeweekse avond zat niemand meer door de gedeelde mailbox te scrollen. De partners waren gestopt met de gewoonte om hem nog even door te kammen, want de agent had alles wat ze moesten zien al doorgezet. Daardoor beantwoordden ze ook geen dingen meer per ongeluk om middernacht, iets waar het operations-team al jaren stilletjes mee zat.
De kleinste versie die je volgende week live kunt zetten
Je hebt de hele pipeline niet nodig om het meeste resultaat te halen. Abonneer je op één gedeelde mailbox via Microsoft Graph. Stuur elk nieuw bericht door één classifier-call met een lijst van vijf categorieën die je uit je hoofd kunt opnoemen. Plaats het resultaat, samen met het originele bericht, in één Teams- of Slack-kanaal met een knop die zegt 'ik pak deze'.
Draai dat een week. Je komt erachter welke vijf categorieën fout zaten, welke mappenstructuur dragend was, en of je team daadwerkelijk een bot vertrouwt om hun inbox te triëren. Daarna kun je gaan nadenken over concepten, SLA's en routing.
Toen we de triage-agent voor het kantoor hierboven bouwden, liepen we erin vast dat de regels eigenlijk geen regels waren. Het was institutioneel geheugen, opgeschreven in mapnamen en regelvoorwaarden. We zijn die kennis uiteindelijk in het CRM gaan opslaan voordat er ook maar één classifier de mailbox zag, en dat is het patroon dat we sindsdien hergebruiken zodra we AI-agents bouwen tegen een verouderde inbox.
Als je deze week één ding doet: open je gedeelde mailbox, sorteer op mapgrootte en vraag de drie grootste mappen wat erin zit. Dat gesprek is waar het echte werk begint.
Kern
Outlook-regels zijn vermomd institutioneel geheugen. Lees eerst de mail, schrijf daarna de agent, en zet de kennis in het CRM waar hij thuishoort.
FAQ
Verzendt de agent zelf antwoorden?
We houden altijd een mens op de verzendknop. Concepten belanden direct in de Outlook van de behandelaar, die controleert en klikt op verzenden. In de eerste twee weken van meten ging 71% van de concepten ongewijzigd de deur uit.
Hoe zit het met AVG als klantmail door een LLM gaat?
De classifier draait op EU-infrastructuur, berichtinhoud wordt nooit verder gelogd dan een hash, en de modelleverancier heeft een getekende verwerkersovereenkomst. Een 'gevoelig'-vlag op een klant in het CRM blokkeert de LLM-call volledig.
Hoe vaak verlopen Microsoft Graph-subscriptions?
Mail change-notification-subscriptions verlopen na ongeveer 70 uur. Draai een renewal-job per uur die elke subscription met minder dan een uur restduur verlengt, en je verliest er nooit een.
Hebben jullie alle 241 Outlook-regels in één keer verwijderd?
Nee. We hebben de agent twee weken parallel laten draaien, zijn routing vergeleken met wat de regels zouden hebben gedaan, en zetten de regels pas uit toen de agent matchte op de categorieën die ertoe deden.