A simple, easy-to-use Python library for accessing the Jmail Data API, the public data API for the Jeffrey Epstein email archive.
No API keys. No rate limits. No authentication. Just data.
pip install jmail-pythonOr with uv:
uv pip install jmail-pythonfromjmailimportJmailClientclient=JmailClient()
# Get emails (slim = no body text, much faster)df=client.emails(slim=True)
print(df.head())
# Full emails with body textdf=client.emails()
# First 100 emails onlydf=client.emails(head=100)
# Document metadatadocs=client.documents()
# Documents with full extracted text (large download)docs=client.documents(include_text=True)| Method | Dataset | Records | Size |
|---|---|---|---|
client.emails() | Emails (full) | 1.78M | 334 MB |
client.emails(slim=True) | Emails (slim) | 1.78M | 41 MB |
client.documents() | Documents | 1.41M | 25 MB |
client.documents(include_text=True) | Documents (full text) | 1.41M | Large |
client.photos() | Photos | 18K | ~1 MB |
client.people() | People | 473 | <100 KB |
client.photo_faces() | Photo Faces | 975 | <100 KB |
client.star_counts() | Star Counts | 414K | ~2 MB |
client.release_batches() | Release Batches | 11 | <10 KB |
client.imessage_conversations() | iMessage Conversations | 15 | <10 KB |
client.imessage_messages() | iMessage Messages | 4,509 | ~172 KB |
All methods return a pandas DataFrame. Pass head=N to any method to only load the first N rows.
client=JmailClient()
# Search emails by sender (case-insensitive)df=client.search_by_sender("ghislaine")
# Search by subjectdf=client.search_by_subject("flight")
# Top 10 most frequent senderstop=client.top_senders(n=10)
print(top)The client uses ETag-based conditional requests to avoid re-downloading unchanged files.
- Cache location:
~/.cache/jmail/ - First download: saves the parquet file and its ETag
- Subsequent requests: sends
If-None-Matchheader, uses cache on 304 Not Modified - Disable:
JmailClient(cache=False) - Clear:
client.clear_cache()
# Always download fresh (no caching)client=JmailClient(cache=False)
# Custom cache directoryclient=JmailClient(cache_dir="/tmp/my-cache")
# Clear the cacheclient.clear_cache()Need raw URLs for use with DuckDB, Polars, or other tools?
client=JmailClient()
# Single dataset URLurl=client.url("emails-slim")
# -> "https://data.jmail.world/v1/emails-slim.parquet"url=client.url("emails-slim", fmt="ndjson.gz")
# -> "https://data.jmail.world/v1/emails-slim.ndjson.gz"# All dataset URLsall_urls=client.urls()The manifest provides dataset metadata and checksums:
client=JmailClient()
manifest=client.manifest()
print(manifest["version"])
print(manifest["datasets"]["emails"]["record_count"])You can query the parquet files directly over HTTP without downloading:
importduckdbresult=duckdb.sql(""" SELECT sender, COUNT(*) as n FROM read_parquet('https://data.jmail.world/v1/emails-slim.parquet') GROUP BY sender ORDER BY n DESC LIMIT 20""").df()
print(result)The archive includes data from three sources:
- House Oversight Committee releases
- Department of Justice releases
- Yahoo account releases
All data is public domain (US government records), served as static Parquet files from Cloudflare R2.
AGPL-3.0. See LICENSE for details.