https://txtfetch.com/extract/email/
Email, headers to attachments, one call.
From/To/Subject headers, the message body, and attached documents all come out together. You don't orchestrate separate steps.
the-problem
Email is a container format wearing a text format's clothes. It has headers, a MIME-encoded body (often both plain-text and HTML parts), and arbitrary attached documents that themselves need extracting. Most extraction tools have no email code path at all. That's especially true for VLM-based tools built around single images or pages. Teams end up hand-rolling header parsing and MIME decoding just to get the text they actually want.
one-request-solution
txtfetch treats email as a first-class format. POST an .eml, Outlook .msg, or .mbox archive, and Apache Tika extracts headers, body, and attachment text together into one plain-text response. The request shape is the same as every other format.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@support-thread.emlimport os
import requests
with open("support-thread.eml", "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
print(r.json()["extracted_text"])import { readFile } from "node:fs/promises";
const file = new Blob([await readFile("support-thread.eml")]);
const form = new FormData();
form.append("file", file, "support-thread.eml");
const res = await fetch("https://api.txtfetch.com/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
body: form,
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
f, err := os.Open("support-thread.eml")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "support-thread.eml")
if err != nil {
panic(err)
}
if _, err := io.Copy(part, f); err != nil {
panic(err)
}
writer.Close()
req, err := http.NewRequest("POST", "https://api.txtfetch.com/v1/extract", &body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}Or skip the download. Pass a url parameter and txtfetch fetches the document server-side:
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/archive/2024-q1.mbox" \
-H "Authorization: Bearer $TXTFETCH_KEY"import os
import requests
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
params={"url": "https://example.com/archive/2024-q1.mbox"},
)
print(r.json()["extracted_text"])const endpoint = new URL("https://api.txtfetch.com/v1/extract");
endpoint.searchParams.set("url", "https://example.com/archive/2024-q1.mbox");
const res = await fetch(endpoint, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
endpoint, err := url.Parse("https://api.txtfetch.com/v1/extract")
if err != nil {
panic(err)
}
q := endpoint.Query()
q.Set("url", "https://example.com/archive/2024-q1.mbox")
endpoint.RawQuery = q.Encode()
req, err := http.NewRequest("POST", endpoint.String(), nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}{
"status": "success",
"extracted_text": "..."
}what-comes-back
That is the shape. This is the text. A real .eml from the benchmark corpus, word-diffed against the raw parser output. Nothing here was written for this page. Every character comes from a committed fixture.
Invoice Follow-up (EML)
raw parser output
From: Priya Shah <priya@example.com>
To: Billing <billing@example.com>
Subject: Re: Invoice #7734
Hi team,
Following up on the invoice below — could you confirm receipt?
Description Qty Amount
Consulting hours 12 $1,800.00
Travel 1 $340.00
Invoice total: $2,140.00
Due date: 2026-08-15
Thanks,
Priya
the human-checked ideal
From: Priya Shah <priya@example.com>
To: Billing <billing@example.com>
Subject: Re: Invoice #7734
Hi team,
Following up on the invoice below — could you confirm receipt?
Description Qty Amount
Consulting hours 12 $1,800.00
Travel 1 $340.00
Invoice total: $2,140.00
Due date: 2026-08-15
Thanks, PriyaSee all ten documents, with the full explanation →
formats-covered
.eml.msg.mbox
faq
- Can I parse a .eml email for an LLM?
- Yes. POST the .eml file to https://api.txtfetch.com/v1/extract and get back headers, body, and any attachment text as one plain-text response. It's ready to feed into a prompt or embedding step.
- Does it support Outlook .msg files?
- Yes, through the same endpoint, with the same response shape. Under the hood, .msg is a completely different container: OLE2 with MAPI property streams, not RFC-822. Read the dedicated .msg page if you're debugging attachment or nesting behavior.
- Can it process a whole .mbox archive?
- Yes. Apache Tika reads .mbox archives and extracts the contained messages' text.
- Are email attachments included in the extracted text?
- Yes. Attached documents are parsed, and their text is folded into the response alongside the message body.
go-further
Send a real .eml through it.
One HTTP call returns the text. Read one in your browser first, for free.
Get an API key →