Email metadata API
POST a message to /v2/convert/email/to/metadata and get back a JSON file describing it: addresses, subject, date, message ID, whether it has an HTML body, the text body and every attachment's name, type and size. Our Outlook sample gave a 1,826-byte JSON.
- Endpoint
- POST /v2/
convert/ email/ to/ metadata - Accepts
- Sample timing
- 1 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/email/to/metadata \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@site-report-week-9.msg \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/email/to/metadata",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("site-report-week-9.msg", "rb"))],
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "site-report-week-9.msg";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/email/to/metadata";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0.001,
"Files": [
{
"FileName": "site-report-week-9.json",
"FileExt": "json",
"FileSize": 1826,
"FileData": "ewogICJhdHRhY2htZW50cyI6…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.
site-report-week-9.json, first 30 of 41 lines
{ "attachments": [ { "content_id": "progress.week9@calloway-reid.example", "content_type": "image/png", "inline": true, "name": "progress-week-9.png", "size": 8201 }, { "content_id": null, "content_type": "text/csv", "inline": false, "name": "rfi-log-week-9.csv", "size": 321 }, { "content_id": null, "content_type": "image/jpeg", "inline": false, "name": "east-wall-opening.jpg", "size": 18032 } ], "bcc": [], "cc": [ "Site Office <site@calloway-reid.example>" ], "date": "Mon, 26 Oct 2026 08:30:00 -0700", "from": [
- Input
- site-report-week-9.msg (48 KB)
- Output
- site-report-week-9.json (1.8 KB)
- Source
- A fictional Outlook message with a weekly building site report, an inline chart and two attachments, written by us and saved to the Outlook file layout by our own script. (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
IgnoreInlineAttachmentsboolean, default false | true drops inline parts from attachments: for the sample, the chart with a content ID goes and the CSV and the photo stay. In an .eml, a part is inline when its Content-Disposition says so. |
FileNamestring | Name of the JSON file without .json. week9-meta returns week9-meta.json; by default it is the message's own name. |
StoreFileboolean, default false | Store the JSON for 3 hours and return a link to it in Files[].Url instead of the data. |
What carries over, and what doesn't
Kept
- Sender, To, Cc and Bcc as arrays of "Name <address>" strings; the sample lists Noor Haddad as sender and both To recipients
- The subject, with encoded words decoded in .eml files (=?utf-8?q?Caf=C3=A9_menu?= reads Café menu)
- The Date: ISO 8601 with the sender's offset for an .eml (2026-11-03T09:15:00+01:00), the saved header text for a .msg (Mon, 26 Oct 2026 08:30:00 -0700)
- The Message-ID, and
has_html_bodysaying whether there is an HTML body - The plain-text body in
text_body, with its line breaks - For each attachment: name, MIME type, decoded size in bytes, whether it is inline, and its content ID
Lost or not yet supported
- Every other header: Reply-To, In-Reply-To, References, Received lines and custom X- headers are not in the JSON.
- The HTML body itself. Only
has_html_bodysays it exists; get the rendering from/v2/convert/eml/to/pdformsg/to/pdf. - Clean text for HTML-only messages. With no text part,
text_bodyis made from the HTML and block boundaries can run together: our test's heading "Menu" and the next paragraph came out as "MenuSoup & bread, $6". - A consistent Message-ID form: an .msg keeps the angle brackets ("<week9.20261026.0830@calloway-reid.example>"), an .eml drops them.
- The Date of a .msg saved without internet headers, such as a draft;
dateandmessage_idare then null. - Attachment contents. Use
/v2/convert/email/to/extractto get the files.
Limits
- One message per request; two files return 400 "this conversion takes exactly one file".
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- A file that is not a message returns 422 "the input file could not be read: this is not an e-mail message". An .eml needs at least one of From, To, Subject, Date or Message-ID.
- A damaged .msg returns 422 "the Outlook message is damaged"; other Office 97-2003 files return 422 as well.
- 120 seconds per request.
Where teams use it
Indexing a mail archive
Run each exported .eml or .msg through this route and load the JSON into a search index or database: who sent it, to whom, when, the subject and the plain text, without writing a MIME or Outlook parser yourself.
Routing inbound mail
Decide what to do with a message before converting it: check attachments for a PDF or a spreadsheet, read the sender, and only then send the message or its files to the routes that need them.
Checking a message before a records transfer
Confirm that each file is a readable message and see how many attachments it carries and how large they are, so a batch can be split or flagged before it is converted to PDF.
Questions
What does the JSON look like?
One object with the keys attachments, bcc, cc, date, from, has_html_body, message_id, subject, text_body and to, in that order, pretty-printed. Address fields are always arrays, even with one entry. A missing subject is an empty string; a missing date, message ID or text body is null.
Why do .eml and .msg dates look different?
They come from different places. An .eml's Date header is parsed and written as ISO 8601 with its UTC offset, such as 2026-10-30T14:05:00-05:00. A .msg's date is copied as text from the internet headers Outlook saved with the message, so it stays in the RFC 5322 form, such as Mon, 26 Oct 2026 08:30:00 -0700.
Are inline images counted as attachments?
Yes, unless you send IgnoreInlineAttachments=true. They appear with inline: true and their content_id, like the sample's progress-week-9.png, so you can tell a signature logo from a file someone attached.
Does it read Bcc?
When the file has it. Our test .eml with a Bcc header returned it in bcc; the sample, a received message saved without one, has an empty list. Nothing can be read that the file does not store.
Convert your first EMAIL file in a minute.