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
curl -X POST https://api.txtfetch.com/v1/extract \
  -H "Authorization: Bearer $TXTFETCH_KEY" \
  -F file=@support-thread.eml
Python
import 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"])
JavaScript
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);
Go
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
curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/archive/2024-q1.mbox" \
  -H "Authorization: Bearer $TXTFETCH_KEY"
Python
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"])
JavaScript
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);
Go
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)

invoice-followup.eml · message/rfc822 · left pane read via Apache Tika, right pane: the human-checked expected.json

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, Priya

See 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 →

Open the free .eml reader →