Skip to content
Writing
Inbound EmailWebhooksLLMPython

Inbound Email Webhook Parsing with LLMs: Converting Unstructured Email to JSON

Turn messy incoming email replies, support tickets, and invoices into strictly validated JSON records using LLM structured outputs and webhooks.

The Chaos of Inbound Email: Forwarding Chains, Disclaimers & Signatures

Processing inbound customer email has historically been one of the most painful challenges in software engineering. Emails arrive as messy multipart MIME payloads containing recursive forwarding headers (---------- Forwarded message ---------), nested blockquotes, corporate confidentiality disclaimers, embedded base64 signatures, and varied phrasing.

Traditional regex patterns and heuristic string parsers break whenever a user replies on mobile or uses an unfamiliar mail client. By pairing inbound webhooks with LLM structured outputs (via OpenAI function calling, Anthropic tool use, or Pydantic schemas), developers can convert messy conversational prose into deterministic, type-safe JSON records in milliseconds.

1. Production FastAPI Webhook Ingestion with Pydantic v2

This service ingests the inbound email webhook, normalizes the raw text and HTML, and uses an LLM structured parser to extract intent, priority, urgency, and actionable next steps.

PYTHON
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel, Field, EmailStr
from openai import OpenAI
import os

app = FastAPI(title="Inbound Email LLM Ingestion Gateway")
client = OpenAI()

class StructuredSupportTicket(BaseModel):
    category: str = Field(description="Billing, Bug Report, Security, or General Inquiry")
    sentiment: str = Field(description="Positive, Neutral, Frustrated, or Urgent")
    customer_intent: str = Field(description="Core objective of the customer message")
    requires_human_reply: bool = Field(description="Whether automated response suffices or human triage needed")
    extracted_entities: dict = Field(default_factory=dict, description="Extracted account IDs, invoice numbers, or URLs")
    suggested_reply: str = Field(description="Drafted empathetic and accurate reply to customer")

@app.post("/webhooks/inbound")
async def handle_inbound_email_webhook(request: Request):
    payload = await request.json()
    from_email = payload.get("from")
    subject = payload.get("subject", "")
    raw_body = payload.get("text") or payload.get("html", "")

    if not from_email or not raw_body:
        raise HTTPException(status_code=400, detail="Missing required email fields")

    # Pass sanitized raw body to LLM structured parser
    completion = client.beta.chat.completions.parse(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": "You are a specialized enterprise email parser. Extract structured facts, categorize intent, and ignore email disclaimers and signature logos.",
            },
            {
                "role": "user",
                "content": f"From: {from_email}\nSubject: {subject}\n\nBody:\n{raw_body[:4000]}",
            },
        ],
        response_format=StructuredSupportTicket,
    )

    ticket = completion.choices[0].message.parsed
    print(f"[PARSED] {from_email} -> {ticket.category} (Urgency: {ticket.sentiment})")

    # Store structured ticket in Postgres or trigger internal workflow
    return {"status": "success", "ticket": ticket.dict()}

Inbound Parsing Strategies: Regex vs Heuristics vs LLM

Parsing StrategyEdge Case ResiliencyLatencyMaintenance Cost
Regex String MatchersExtremely fragile (Breaks on client variation)< 1msHigh (Constant rule tweaking)
HTML DOM CleanersModerate (Struggles with quote stripping)2–5msModerate
LLM Structured OutputsVirtually immune to phrasing or client formatting400–900msMinimal (Self-healing schema parsing)
Free plan

Building AI agents that send email?

Scoped API keys, per-key recipient allowlists, approval mode and a hosted MCP server with ten tools — on the free plan, without a card.