An ops manager at a 25-person UK distributor started every morning the same way: opening 80 emails, reading for the purchase order number, the delivery date, the quantity, the product code, the contact name, and four other fields, then typing each into a spreadsheet. Four hours of her day. Every day. The emails contained all the information — it just lived inside prose, not in fields.
We built the extraction pipeline. It now processes each email in under three seconds, pulls all nine fields as structured JSON, and routes anything below a confidence threshold to a human review queue. That queue catches 8% of messages. The other 92% write directly to the connected systems — a Google Sheet for the order register, Xero for invoices.
What unstructured email inboxes cost UK ops teams: the six-hour weekly extraction tax most finance and ops leads have stopped noticing
Before writing a line of code, we measured the actual cost. Here is what we found in the distributor engagement:
- 80 inbound emails per day containing order-relevant content
- Average 2.5 minutes per email to read and transcribe nine fields
- Total manual extraction: approximately 3.3 hours per day, 16.5 hours per week across the team
- Downstream error rate from manual entry: 4.7%, surfaced from reconciliation failures when the warehouse could not match a product code three days after the order
The six-hour figure most ops leads quote is an undercount. It excludes the reconciliation work generated by the 4.7% error rate — finding the wrong product code, re-querying the supplier, correcting the spreadsheet, notifying the warehouse. That secondary labour adds another one to two hours per week.
For a related look at the same pattern in invoice processing, see our invoice OCR pipeline build.
Email parsing vs LLM extraction: when regex rules break and when an LLM earns its compute cost
Rule-based parsing works well when data is always in the same place. If every supplier sends a CSV attachment with a fixed column order, write a CSV parser. The problem is that email is a human communication medium and human communication varies.
The same supplier writes "PO: 4456" in one email and "please raise purchase order number four four five six" in the next, or forwards a chain where the original order is buried after three reply headers. Here is where the decision sits:
| Scenario | Rule-based parser | LLM extraction |
|---|---|---|
| Fixed-format CSV attachment | Better — faster, cheaper, deterministic | Overkill |
| Structured HTML invoice email | Better — DOM traversal is reliable | Marginal gain |
| Free-prose email body | Fails at scale | Right tool |
| Mixed formats from the same sender | Fragile, grows maintenance debt | Handles gracefully |
| Forwarded chains with quoting | Breaks on header variations | Requires preprocessing |
| Non-English with mixed codes | Needs separate rules per language | Single prompt handles |
The genuine alternative: Parseur and similar rule-based tools solve the structured-format problem at a fraction of LLM API cost. Use them when your emails follow consistent templates — the LLM approach costs ten times more for no accuracy gain. LLM extraction earns its compute cost when format varies, the sender population is large, or fields require interpretation.
For the distributor, 14 different supplier formats and 200-plus customers made rule-based parsing impractical to maintain.
Prompt architecture for email extraction: pulling order numbers, quantities, dates, and contact names reliably in structured output
The critical decision is using a model that supports structured outputs — guaranteed JSON conforming to a schema you define — rather than asking the model to "return JSON" in plain text and parsing the response yourself. Plain-text JSON breaks on edge cases: models add markdown fences around the object, write commentary before the opening bracket, or drop closing braces on long outputs. Structured output APIs guarantee the shape.
Schema deployed for the distributor:
{
"type": "object",
"properties": {
"po_number": { "type": ["string", "null"] },
"product_codes": { "type": "array", "items": { "type": "string" } },
"quantities": { "type": "array", "items": { "type": "number" } },
"requested_delivery_date": { "type": ["string", "null"], "format": "date" },
"confirmed_delivery_date": { "type": ["string", "null"], "format": "date" },
"unit_price": { "type": ["number", "null"] },
"customer_reference": { "type": ["string", "null"] },
"contact_name": { "type": ["string", "null"] },
"contact_email": { "type": ["string", "null"] },
"confidence_notes": { "type": "string" }
},
"required": ["po_number", "product_codes", "quantities", "confidence_notes"]
}
System prompt (abbreviated):
You are an order extraction assistant for a UK distribution company.
Extract all purchase order fields from the email text below.
If a field is absent or ambiguous, return null — never invent values.
Normalise dates to ISO 8601 (YYYY-MM-DD). Use the email received date
as the anchor for relative references such as "next Tuesday" or "wk 34".
In confidence_notes, list any field where you are uncertain, with a
one-line reason. Leave confidence_notes empty if all fields are clear.
The confidence_notes field does double duty: extraction output and routing signal. Empty string means auto-write. Any text means human review.
Confidence scoring for extracted fields: which values to auto-write and which to queue for human review before downstream write
The confidence_notes string drives the routing logic in n8n. The condition is simple: non-empty string routes to the review queue; empty string routes to the auto-write step.
The review queue is a Google Sheet with five columns: received timestamp, email subject, extracted JSON, confidence note, and reviewer decision (approve / edit / reject). Each review takes 20–40 seconds rather than 2.5 minutes for a full manual extraction.
In the first two weeks of production, we reviewed 100% of extractions to build a ground-truth dataset. After that period:
- Auto-approved email accuracy: 98.9% on individual fields
- Review queue rate: 8% of total volume
- Weekly time returned: approximately 14 hours across the ops team
Do not collapse the review queue too quickly. The 8% rate is a signal, not a failure. It catches the emails where a customer sent two orders in one message, where a delivery date referenced "the usual lead time" without a number, or where the product code appeared in a signature block rather than the order body.
For the same confidence-routing pattern applied to scanned documents, see OCR with human-in-the-loop.
Multi-format email handling: plain text, HTML bodies, and forwarded chains that break naive extraction patterns
Three email formats account for most of the production edge cases.
Plain text is the simplest case. Strip signatures — look for -- on its own line, or common footer phrases like "Kind regards" followed by a name block — then pass the cleaned body to the extraction prompt.
HTML bodies require tag stripping before extraction. Use a library — html-to-text in Node or bleach in Python — not a regex. Regex on HTML fails on nested elements, encoded characters, and CSS inline styles. The stripped plain text then goes through the same prompt as a plain-text email.
Forwarded chains are where most pipelines fail. A forwarded chain contains multiple email bodies separated by headers such as ---------- Forwarded message --------- or the standard Outlook block (From: ... Sent: ... To: ... Subject:). Naive extraction pulls contact fields from the wrong level, or merges conflicting delivery dates from an original order and a later amendment.
The preprocessing step we ship: split the chain at each forwarding or reply header, assign a received timestamp to each segment parsed from the header, and select the oldest segment as the source of truth. Pass only that segment to the extraction prompt with a note: "Extract from this original message only. Ignore any replies or amendments." This eliminates the error class where the model extracts the forwarding manager's name rather than the customer's.
Downstream routing: writing extracted data to Xero, HubSpot, Notion, or a Google Sheet automatically on extraction
Once extraction passes the confidence check, field values write to their destinations. We use n8n for orchestration: conditional routing without custom code, native nodes for most downstream targets.
Routing map for the distributor build:
- New PO number, not in the order register: Create a new Google Sheets row, send a Slack notification to the procurement lead
- PO number already exists: Update the existing row with any amended delivery date or quantity — treat it as a supplier amendment
- Invoice fields present (unit price, confirmed delivery date): Write to Xero draft invoices via the Xero accounting API
- Contact email matches a HubSpot contact record: Update the last-order date property on the contact
For a parallel pattern applied to sales call recordings — extracting BANT fields into CRM records rather than order data into ops systems — see Meeting Transcript to CRM: Automate BANT Extraction.
The write step executes only after human approval for anything in the review queue. In n8n, this is a webhook wait node: the workflow pauses, sends the reviewer a notification with a direct link to the review row, and resumes when they submit their decision. Maximum queue hold is 48 hours before an escalation fires.
For the three-way matching logic that runs downstream after extraction — PO against goods receipt against invoice — see AI purchase order automation.
UK GDPR compliance for email-to-data pipelines: retention rules, lawful basis for processing, and subject access implications
Email-to-data pipelines process personal data the moment they extract contact names and email addresses. That triggers obligations under the UK GDPR and the Data Protection Act 2018.
Lawful basis. For B2B order processing, the most defensible basis is legitimate interests under Article 6(1)(f) — you have a genuine operational interest in processing purchase order information efficiently. Document this basis in your Records of Processing Activities before the pipeline goes live. The ICO's legitimate interests guidance covers the three-part balancing test you need to complete and record.
Retention. Do not hold raw email text in the pipeline database longer than necessary. In the distributor build, raw email bodies are deleted from the pipeline database after 30 days. Extracted structured data is retained for seven years, in line with Companies Act requirements for financial records.
Subject access requests. If a supplier or customer submits a SAR, you must retrieve all personal data held about them — including structured data extracted from their emails. Build a search path against the extraction database by contact email at the outset; retrofitting it is expensive. For a complete walkthrough of automating the response workflow within the 30-day window, see GDPR DSAR Automation for UK SMEs.
Data processor agreements. If you use an LLM API for extraction, the API provider processes personal data on your behalf. Obtain a signed Data Processing Agreement before the pipeline handles any live email — most major providers offer a standard DPA on request.
What changed in 2025–2026: Gmail and Outlook native AI extraction, structured output models, and GPT-4o with PDF APIs
Three developments shifted the economics of email extraction in the past 18 months.
Structured output support became reliable at scale. Both gpt-4o and Claude 3.5+ now support guaranteed JSON schema conformance via their APIs. Before mid-2024, structured extraction required prompt engineering to coerce JSON output, plus a repair fallback for malformed responses. That failure mode is now rare for well-formed prompts, which removes a meaningful source of production incidents.
Gmail and Outlook added native AI extraction features. Microsoft Copilot in Outlook and Gemini in Gmail can suggest field values from email content — useful for individual productivity, not for automated pipelines. Neither offers a programmatic API for bulk extraction that writes to downstream systems without a human step.
GPT-4o PDF and image input eliminates a separate OCR step. The gpt-4o model now accepts PDFs and images natively. For email pipelines where the purchase order arrives as a PDF attachment, a single API call receives the email body text and the PDF together, and the model extracts fields from whichever source contains them — cutting per-email latency on attachment-heavy inboxes by approximately 40% versus the OCR-then-extract pattern. For extracting structured data from invoice PDF attachments specifically, see Invoice Data Extraction: AI Pipelines Beyond Basic OCR.
Good / Bad / Ugly: three email extraction architectures and their accuracy at production volume
Good: LLM extraction with schema-enforced output and a confidence review queue
Accuracy at production volume: 98.9% on auto-approved fields. Review queue rate: 8%. Cost per email: £0.003–£0.006 depending on email length using gpt-4o. Maintenance burden: low — prompts update as edge cases emerge, no per-sender rules to manage. This is the architecture we run in production.
Bad: Regex and rule-based parsing applied to unstructured free-prose email
Works for the subset of your inbox that is consistently templated — often 20–40% of real-world volume. Fails silently on the rest, writing null or wrong values without any uncertainty signal. Because there is no confidence output, there is no review queue, and errors propagate unchecked to downstream systems. Maintenance debt grows every time a supplier changes their email format, and that change may not surface until accounts flags a reconciliation failure two weeks later.
Ugly: Asking the LLM to return JSON in plain text, then parsing it yourself
This is the most common first implementation. It works in development and breaks in production. Models add markdown fences around the JSON, write preamble before the opening bracket, or drop closing braces on long email bodies. Your downstream parser fails intermittently — not consistently, which makes it harder to debug than a clean error. The fix is to switch to structured output APIs. The inconsistency cost is not worth the marginally simpler implementation.