← Blog

Email automation

Email agents for translation quotes: 4 hours to 9 minutes

It is 10:47 on a Tuesday. A 78-page tender drops into a Nijmegen translation agency inbox, deadline noon, three language pairs, and the PM is on her first coffee.

Jacob Molkenboer· Founder · A Brand New Company· 13 Dec 2024· 8 min
Cream envelope with chartreuse ribbon on ivory linen, brass bell, index card, folded paper edge, side light.

It is 10:47 on a Tuesday in Nijmegen. A 78-page tender from a German ministry lands in the agency inbox. Three language pairs. Deadline noon. A Trados memory exists from the previous contract, but the project manager has not opened the file yet, and the inbox already has nine quote requests stacked behind it.

This was the agency's normal Tuesday. A 37-person team, mostly translators and reviewers, with two project managers handling intake. Quote turnaround averaged about four hours per tender. Some quotes never went out because the deadline passed first.

This post is the build log for the email agent that took quote turnaround from four hours to nine minutes. No prompt theatre. Just a Claude-driven pipeline with a hard human gate at the end. We will walk through what it parses, where the Trados pricing comes from, what broke in the first month, and the cost of running it.

The shape of an incoming tender

A translation tender is not a brief. It is a PDF that mixes legal boilerplate, source-text exhibits, deadlines buried in tables, and a price grid the bidder is supposed to fill in. For a single tender, the agency's PM had to do five things by hand:

  • Find the language pairs. Sometimes listed clearly, sometimes implied by which source-text exhibits are attached.
  • Find the deadline. Often two deadlines: bid submission and project delivery.
  • Run the source text through Trados to get a word count, split by new, fuzzy, and 100% match against existing memory.
  • Apply the agency's per-pair rate card and the discount tiers in the tender's price grid.
  • Draft a reply with a project plan, team assignment, and quoted total in EUR.

Steps one, two, and five are reading-and-writing work. Step three is a Trados job. Step four is arithmetic with a lookup table. The whole thing is mostly about not making a mistake on the lookup table, and not missing a clause that says "we will not accept files delivered after 17:00 CET on the day before the published deadline."

Architecture

The agent is four services and a queue:

  1. An IMAP listener on the quote-intake mailbox. It watches with IDLE so new mail surfaces in seconds.
  2. A PDF extractor. PyMuPDF for digital PDFs, Tesseract for the scanned ones.
  3. A Claude extraction step that returns structured JSON: language pairs, deadlines, scope, special clauses, contact info.
  4. A Trados word-count step that calls the internal CAT server over its CLI.
  5. A draft-reply step that produces a Dutch or German cover letter with the quote attached, then drops it into the PM's Gmail Drafts folder. Nothing is ever sent without a human click.

PDF parsing, and why pdftotext alone isn't enough

The first version used pdftotext from Poppler and that handled about 70% of incoming tenders cleanly. The other 30% were either scanned PDFs (ministry procurement portals love a scanned signed cover page) or PDFs where the price grid is a table that pdftotext flattens into a soup of digits.

The fix was a small router: try a digital extract first, fall back to OCR if the page yields less than fifty characters of extractable text, and use PyMuPDF for layout-preserving extraction of pages that look table-heavy.

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

The extraction prompt, and why we use tool use

We do not ask Claude to "summarise the tender". We ask it to fill a strict schema. Anthropic's tool-use API lets us define the schema as a tool definition that Claude must satisfy. Free-text replies are off the table.

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"]
    }
}

Many tenders reference ISO 17100 compliance, which the agency holds. We list that requirement explicitly in special_clauses because the draft reply changes when the clause is present. The PM has to assign a reviewer separately from the translator, and the quote line items split.

The Trados step

Word counts are not wc -w. A 12,000-word source text might bill as 4,200 effective words once you subtract 100% matches from the client's existing memory and weight the fuzzy bands. The agency uses Trados Studio's command-line analysis with their per-client translation memory. We pipe the extracted source text into a temp project and read the XML report back.

The agent does not invent the price. It calls the CAT server, reads the analysis, applies the agency's rate card (a YAML file under version control), and produces a quote in EUR. Same numbers a PM would get by hand. This is the line of defence against hallucinated prices.

Warning

Never let the LLM compute the quote total. Extract the inputs, do the arithmetic in code, and have the model write the cover letter. If it invents 4,200 instead of 12,000, you will find out from a finance team that has already paid the freelancer.

The draft reply

This is the only step where Claude is asked to write something the customer will read. The draft includes the quoted total, the team assignment (translator plus reviewer per pair), the proposed delivery date based on the agency's capacity calendar, and a polite caveat sentence if the bid deadline is under 24 hours away.

The reply lands in the PM's Gmail Drafts. It is never auto-sent. The PM reviews, edits the tone if needed, attaches the signed PDF quote that the CAT pipeline generated, and presses Send. On a normal tender, the review takes 90 seconds. On a tender with a tricky clause, it takes longer. That is fine. The point is not to remove the PM. The point is to remove the four hours of reading and arithmetic.

What broke in the first month

Three things broke, and they are worth sharing because they will break in your build too.

Multilingual tenders confused the language detector

An EU tender published in three languages plus an annex in a fourth made the agent guess wrong about the source language. The fix was to pre-segment pages by detected language before extraction, then ask Claude to extract per-language-block rather than per-document.

Table extraction silently merged columns

Price grids with merged cells came out as garbage after pdftotext. PyMuPDF's get_text("blocks") plus a small post-processor that reconstructs columns by x-coordinate fixed most cases. The rest we routed to a Claude vision pass on the page image, which is slow but reliable.

Deadlines in time zones nobody specified

"by 17:00" is ambiguous. The agent now defaults to CET unless the tender explicitly says otherwise, and surfaces a yellow flag on the draft if the deadline is parsed without a stated time zone. The PM sees the flag and confirms before the reply goes out.

Numbers after three months

  • Median time from tender arrival to draft-in-PM-inbox: 9 minutes. Was about 4 hours (range 90 minutes to 7 hours, depending on PM workload).
  • Quote-to-win rate: up roughly a third. The team attributes most of this to bidding on tenders they would previously have skipped for time reasons.
  • Cost per tender processed: roughly EUR 0.40 in Claude API spend. Cheaper than a coffee per quote.
  • PM time recovered: about 11 hours per week across the two PMs. They are now doing more relationship work with key clients.

The nine-minute number is not a magic ceiling. About half of the elapsed time is the Trados analysis itself: the CAT server runs on a single machine and is single-threaded per project. If we sharded the translation memories, the median would drop further. We did not, because nine minutes was already well below the response window the agency wanted.

What this is not

This is not a generic "AI does my email" tool. The pipeline knows the exact shape of one document type, calls a real CAT tool for pricing, and has a hard human gate. It would not work on a fashion brand's PR inbox. It works here because the agency's quote process was already structured. We just removed the manual labour from the structured parts.

There is a recurring thread, currently sitting near the top of Hacker News, that asks what tools people have built for themselves since the advent of capable LLMs. The common pattern in the replies: take one workflow you already understand, automate the boring 80%, keep a human on the 20% that requires judgment. Skip the generic chatbot.

Takeaway

The LLM extracts inputs. Code does the arithmetic. The model drafts the cover letter. A human hits Send. Move any one of those four lines and the system breaks.

If you are about to build one

A few things we would tell ourselves on day one:

  • Pick the document type before you pick the model. If you cannot describe the schema you want to extract on a whiteboard, you cannot build the agent.
  • Put the extraction schema under version control. It will change weekly for the first month.
  • Do the arithmetic in code, not in the prompt. Always.
  • Drafts folder, not Sent folder. For at least three months. Maybe forever.
  • Log the inputs and outputs of every step. When a quote goes out wrong, you want to replay it.

When we built this email agent for the Nijmegen team, the part we underestimated was the Trados integration: parsing the analysis XML cleanly across three different memory formats took longer than the LLM work. We solved it with a small adapter layer per CAT tool, and froze the rate-card YAML so finance owns the prices, not engineering.

Today's smallest move: open your last ten outgoing quotes and write down, on one sheet of A4, what you copy-pasted from where. That A4 is your extraction schema. The rest is plumbing.

Key takeaway

Extract the inputs with the LLM, do the arithmetic in code, draft into Gmail Drafts. Never let the model invent the price or hit Send.

FAQ

Can the agent send quotes without a human?

Technically yes. We deliberately do not. Every reply lands in Gmail Drafts and a project manager presses Send. The 90-second review catches model drift, time-zone mistakes, and clause edge cases.

Why not let Claude compute the price directly?

LLMs are bad at arithmetic at scale and bad at lookup tables. The agent extracts inputs, then code multiplies word counts against a YAML rate card. Finance owns the YAML. Engineering owns the pipeline.

Does this work with memoQ or other CAT tools?

Yes. The Trados step is an adapter. Any CAT tool that exposes a CLI analysis or an XML report will plug in. The agency runs Trados; we wrote a memoQ adapter for a second client in about two days.

What about scanned PDFs from older procurement portals?

The extractor falls back to Tesseract OCR when a page yields fewer than fifty characters of extractable text. Dutch, English, and German language packs cover most ministerial tenders the agency sees.

How much does it cost to run per month?

Roughly EUR 0.40 per tender in Claude API spend, plus the existing CAT server and Gmail. At about 200 tenders a month, total LLM cost lands under EUR 100. Most of the savings is reclaimed PM hours.

email automationai agentsautomationcase studyintegrationsworkflow

Building something?

Start a project