Email automation
Email-agents voor vertaaloffertes: van 4 uur naar 9 minuten
Het is dinsdagochtend 10:47. Een tender van 78 paginas valt binnen bij een Nijmeegs vertaalbureau, drie taalcombinaties, deadline 12:00, en de PM zit nog aan haar eerste koffie.

Het is dinsdag 10:47 in Nijmegen. Een tender van 78 paginas van een Duits ministerie valt binnen in de bureau-inbox. Drie taalcombinaties. Deadline 12:00. Er bestaat een Trados-memory van het vorige contract, maar de project manager heeft het bestand nog niet geopend, en in de inbox staan al negen offerteaanvragen op haar te wachten.
Dit was de normale dinsdag van het bureau. Een team van 37 mensen, vooral vertalers en revisoren, met twee project managers die de intake doen. De gemiddelde doorlooptijd voor een offerte lag rond de vier uur per tender. Sommige offertes gingen nooit de deur uit omdat de deadline eerder verstreek.
Dit bericht is het bouwlog van de email-agent die de offertedoorlooptijd terugbracht van vier uur naar negen minuten. Geen prompt-theater. Gewoon een pipeline aangestuurd door Claude, met een harde menselijke check aan het eind. We lopen door wat hij parseert, waar de Trados-pricing vandaan komt, wat de eerste maand kapotging, en wat het kost om het te draaien.
De vorm van een binnenkomende tender
Een vertaaltender is geen briefing. Het is een PDF die juridische boilerplate, bijlages met brontekst, deadlines verstopt in tabellen en een prijsraster dat de inschrijver moet invullen door elkaar gooit. Voor één tender moest de PM van het bureau vijf dingen met de hand doen:
- De taalcombinaties vinden. Soms staan ze duidelijk vermeld, soms blijkt het pas uit welke bijlages met brontekst zijn meegestuurd.
- De deadline vinden. Vaak zijn het er twee: inschrijving en projectlevering.
- De brontekst door Trados halen voor een woordtelling, gesplitst in nieuw, fuzzy en 100%-match tegen de bestaande memory.
- De ratecard per taalcombinatie van het bureau toepassen plus de kortingsstaffel uit het prijsraster van de tender.
- Een antwoord opstellen met een projectplan, teaminzet en het offertebedrag in EUR.
Stappen één, twee en vijf zijn lees- en schrijfwerk. Stap drie is een Trados-klus. Stap vier is rekenwerk met een lookup-tabel. De hele oefening draait er vooral om geen fout te maken in die tabel, en geen clausule te missen die zegt: "we accepteren geen bestanden geleverd na 17:00 CET op de dag voor de gepubliceerde deadline."
Architectuur
De agent bestaat uit vier services en een queue:
- Een IMAP-listener op de offerte-inboxmailbox. Hij luistert met IDLE, dus nieuwe mail komt binnen seconden boven.
- Een PDF-extractor. PyMuPDF voor digitale PDF's, Tesseract voor de gescande.
- Een Claude-extractiestap die gestructureerde JSON teruggeeft: taalcombinaties, deadlines, scope, bijzondere clausules, contactgegevens.
- Een Trados-woordtellingstap die de interne CAT-server via de CLI aanroept.
- Een conceptantwoord-stap die een Nederlandse of Duitse begeleidende brief opstelt met de offerte als bijlage, en die in de map Concepten in Gmail van de PM zet. Niets gaat ooit weg zonder dat een mens klikt.
PDF parsen, en waarom pdftotext in zijn eentje niet genoeg is
De eerste versie gebruikte pdftotext uit Poppler en dat handelde zo'n 70% van de binnenkomende tenders schoon af. De andere 30% waren of gescande PDF's (inkoopportalen van ministeries zijn dol op een gescande, ondertekende voorpagina), of PDF's waarin het prijsraster een tabel is die pdftotext plat slaat tot een soep van cijfers.
De oplossing was een kleine router: probeer eerst een digitale extractie, val terug op OCR als een pagina minder dan vijftig tekens leesbare tekst oplevert, en gebruik PyMuPDF voor layoutbehoudende extractie van pagina's die tabel-zwaar lijken.
import fitz # PyMuPDF
import pytesseract
from pdf2image import convert_from_path
def extract_pages(path: str) -> list[dict]:
"""One dict per page with text plus a hint about how it was extracted."""
doc = fitz.open(path)
out = []
for i, page in enumerate(doc):
text = page.get_text("text").strip()
if len(text) < 50:
# Likely scanned. OCR this page only.
img = convert_from_path(
path, first_page=i + 1, last_page=i + 1, dpi=300
)[0]
text = pytesseract.image_to_string(img, lang="nld+eng+deu")
mode = "ocr"
else:
mode = "digital"
out.append({"page": i + 1, "text": text, "mode": mode})
return out
De extractie-prompt, en waarom we tool use gebruiken
We vragen Claude niet om "de tender samen te vatten". We vragen hem een streng schema te vullen. Met de tool-use-API van Anthropic kun je het schema definiëren als tool-definition waar Claude aan moet voldoen. Vrije-tekst-antwoorden zijn van tafel.
TENDER_SCHEMA = {
"name": "record_tender",
"description": "Extract the structured tender data we need to quote.",
"input_schema": {
"type": "object",
"properties": {
"language_pairs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {"type": "string", "description": "ISO 639-1, e.g. de"},
"target": {"type": "string"},
"approx_words": {"type": "integer", "description": "0 if unknown"}
},
"required": ["source", "target"]
}
},
"bid_deadline_iso": {"type": "string", "description": "ISO-8601 with TZ"},
"delivery_deadline_iso": {"type": "string"},
"client_org": {"type": "string"},
"domain": {"type": "string", "description": "legal, medical, marketing, etc."},
"special_clauses": {
"type": "array",
"items": {"type": "string"},
"description": "ISO 17100, sworn translator, NDA, etc."
},
"confidence": {"type": "number", "description": "0..1 self-rated"}
},
"required": ["language_pairs", "bid_deadline_iso", "confidence"]
}
}
Veel tenders verwijzen naar ISO 17100-compliance, die het bureau heeft. We zetten die eis expliciet in special_clauses, omdat het conceptantwoord verandert zodra de clausule erin staat. De PM moet een revisor los van de vertaler inzetten, en de regels op de offerte splitsen.
De Trados-stap
Woordtellingen zijn geen wc -w. Een brontekst van 12.000 woorden kan factureerbaar uitkomen op 4.200 effectieve woorden zodra je 100%-matches uit de bestaande memory van de klant aftrekt en de fuzzy banden weegt. Het bureau gebruikt de command-line-analyse van Trados Studio met hun translation memory per klant. We pipen de geëxtraheerde brontekst in een tijdelijk project en lezen het XML-rapport terug.
De agent verzint geen prijs. Hij roept de CAT-server aan, leest de analyse, past de ratecard van het bureau toe (een YAML-bestand onder version control) en produceert een offerte in EUR. Dezelfde getallen die een PM met de hand zou krijgen. Dit is de verdedigingslinie tegen gehallucineerde prijzen.
Laat de LLM nooit het offertetotaal uitrekenen. Trek de inputs eruit, doe het rekenwerk in code, en laat het model de begeleidende brief schrijven. Verzint hij 4.200 in plaats van 12.000, dan hoor je dat van een finance team dat de freelancer al heeft uitbetaald.
Het conceptantwoord
Dit is de enige stap waarin we Claude vragen iets te schrijven dat de klant gaat lezen. Het concept bevat het offertebedrag, de teaminzet (vertaler plus revisor per taalcombinatie), de voorgestelde leverdatum op basis van de capaciteitskalender van het bureau, en een beleefde voorbehoudzin als de inschrijfdeadline binnen 24 uur ligt.
Het antwoord landt in de Gmail-concepten van de PM. Er wordt nooit automatisch verzonden. De PM leest, past de toon aan als dat nodig is, hangt de ondertekende PDF-offerte die de CAT-pipeline heeft gegenereerd erbij, en drukt op Verzenden. Bij een normale tender duurt het nakijken 90 seconden. Bij een tender met een lastige clausule duurt het langer. Dat is prima. Het doel is niet om de PM eruit te halen. Het doel is om de vier uur lezen en rekenen eruit te halen.
Wat de eerste maand kapotging
Drie dingen gingen kapot, en die zijn het delen waard, want bij jou gaan ze ook stuk.
Meertalige tenders brachten de taaldetectie in de war
Een EU-tender gepubliceerd in drie talen plus een bijlage in een vierde liet de agent verkeerd gokken over de brontaal. De oplossing: pagina's vooraf segmenteren op gedetecteerde taal voordat we extraheerden, en Claude dan per taalblok laten extraheren in plaats van per document.
Tabel-extractie voegde stilletjes kolommen samen
Prijsrasters met samengevoegde cellen kwamen er als rotzooi uit na pdftotext. De get_text("blocks") van PyMuPDF plus een kleine post-processor die kolommen op basis van x-coördinaat reconstrueert loste de meeste gevallen op. De rest stuurden we naar een Claude-vision-pas op de pagina-afbeelding. Traag, maar betrouwbaar.
Deadlines in tijdzones die niemand vermeldde
"Voor 17:00" is ambigu. De agent valt nu standaard terug op CET tenzij de tender expliciet iets anders zegt, en zet een gele vlag op het concept als de deadline zonder vermelde tijdzone is geparseerd. De PM ziet de vlag en bevestigt voordat het antwoord de deur uit gaat.
De cijfers na drie maanden
- Mediane tijd van binnenkomst tender tot concept in PM-inbox: 9 minuten. Was rond de 4 uur (variatie 90 minuten tot 7 uur, afhankelijk van werkdruk PM).
- Win-percentage op offertes: omhoog met ongeveer een derde. Het team schrijft dat grotendeels toe aan inschrijven op tenders die ze vroeger uit tijdgebrek hadden overgeslagen.
- Kosten per verwerkte tender: ongeveer EUR 0,40 aan Claude-API-verbruik. Goedkoper dan een koffie per offerte.
- Teruggewonnen PM-tijd: ongeveer 11 uur per week voor de twee PM's samen. Ze doen nu meer relatiewerk met key accounts.
De negen minuten zijn geen magisch plafond. Ongeveer de helft van de doorlooptijd is de Trados-analyse zelf: de CAT-server draait op één machine en is single-threaded per project. Als we de translation memories zouden sharden, zou de mediaan verder zakken. We hebben dat niet gedaan, want negen minuten zat al ruim onder het reactievenster dat het bureau wilde.
Wat dit niet is
Dit is geen generieke "AI doet mijn mail"-tool. De pipeline kent de exacte vorm van één documenttype, roept een echte CAT-tool aan voor de prijs, en heeft een harde menselijke check. Op de PR-inbox van een modemerk zou hij niet werken. Hier werkt hij omdat het offerteproces van het bureau al gestructureerd was. We hebben alleen de handarbeid uit de gestructureerde delen weggehaald.
Er staat een terugkerende thread, op dit moment bovenaan Hacker News, waarin gevraagd wordt welke tools mensen voor zichzelf hebben gebouwd sinds er capabele LLM's zijn. Het patroon in de reacties: pak één workflow die je al begrijpt, automatiseer de saaie 80%, en zet een mens op de 20% die om oordeel vraagt. Sla de generieke chatbot over.
De LLM trekt de inputs eruit. Code doet het rekenwerk. Het model schrijft de begeleidende brief. Een mens drukt op Verzenden. Verschuif één van die vier regels en het systeem ligt eruit.
Als je er zelf eentje gaat bouwen
Een paar dingen die we onszelf op dag één zouden vertellen:
- Kies het documenttype voordat je het model kiest. Kun je het schema dat je wilt extraheren niet op een whiteboard beschrijven, dan kun je de agent niet bouwen.
- Zet het extractieschema onder version control. De eerste maand verandert het wekelijks.
- Reken in code, niet in de prompt. Altijd.
- Map Concepten, niet de map Verzonden. Voor minstens drie maanden. Misschien voor altijd.
- Log de inputs en outputs van elke stap. Als een offerte verkeerd de deur uitgaat, wil je hem kunnen terugspelen.
Toen we deze email-agent bouwden voor het Nijmeegse team, hebben we de Trados-integratie onderschat: het schoon parsen van de analyse-XML over drie verschillende memory-formaten kostte meer tijd dan het LLM-werk. We hebben dat opgelost met een kleine adapterlaag per CAT-tool, en de ratecard-YAML bevroren zodat finance eigenaar is van de prijzen, niet engineering.
De kleinste stap voor vandaag: open je laatste tien uitgegane offertes en schrijf op één A4'tje op wat je waarvandaan hebt gekopieerd en geplakt. Dat A4'tje is je extractieschema. De rest is leidingwerk.
Kern
Trek de inputs eruit met de LLM, reken in code, schrijf het concept in Gmail Concepten. Laat het model nooit de prijs verzinnen of op Verzenden drukken.
FAQ
Kan de agent offertes versturen zonder dat er een mens aan te pas komt?
Technisch wel. We doen het bewust niet. Elk antwoord landt in Gmail Concepten en een project manager drukt op Verzenden. Die 90 seconden nakijken vangt model drift, tijdzonefouten en clausule-edge-cases af.
Waarom laat je Claude niet direct de prijs uitrekenen?
LLM's zijn slecht in rekenen op schaal en slecht in lookup-tabellen. De agent trekt de inputs eruit, daarna vermenigvuldigt code de woordtellingen tegen een YAML-ratecard. Finance is eigenaar van de YAML. Engineering is eigenaar van de pipeline.
Werkt dit ook met memoQ of andere CAT-tools?
Ja. De Trados-stap is een adapter. Elke CAT-tool met een CLI-analyse of een XML-rapport sluit aan. Het bureau gebruikt Trados; voor een tweede klant hebben we in zo'n twee dagen een memoQ-adapter geschreven.
Hoe zit het met gescande PDF's van oudere inkoopportalen?
De extractor valt terug op Tesseract OCR als een pagina minder dan vijftig tekens leesbare tekst oplevert. De taalpakketten Nederlands, Engels en Duits dekken de meeste ministeriële tenders die het bureau ziet.
Wat kost het per maand om te draaien?
Ongeveer EUR 0,40 per tender aan Claude-API-verbruik, plus de bestaande CAT-server en Gmail. Bij zo'n 200 tenders per maand komen de totale LLM-kosten onder EUR 100 uit. Het grootste deel van de besparing zit in teruggewonnen PM-uren.