Skip to content

feat(formats): add email (EML) input - #52

Merged
chaliy merged 20 commits into
mainfrom
claude/project-thread-3z7tjs
Oct 27, 2026
Merged

chaliy merged 20 commits into
mainfrom
claude/project-thread-3z7tjs

Conversation

@chaliy

@chaliy chaliy commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

What changed

New email feature (in all_formats, so CLI and Python/Node bindings get it) converts .eml / message/rfc822 files to Markdown:

  • Subject becomes the # heading and the title, followed by From / To / Cc / Date lines.
  • The plain-text body is preferred. When a message has only an HTML body, it goes through the HTML parser with the same stripping and hidden-content rules as web pages.
  • Attachments and nested messages are listed by name, type and size, never parsed.
  • New Format::Eml ("eml"), Error::EmailExtract, ExtractionStats::attachments.

The parser shares the call's Limits budget (nodes, timeout, output cap). Header values are folded, so an encoded newline cannot inject a line.

Why

This is part of the cheap-formats roadmap initiative (text/Markdown #21, CSV/TSV #44 and ipynb #50 are already merged). Email is a common input for agents.

Before / After

$ markkit extract --file a.eml
# before
Error: Unsupported format: unknown
# after
# Hi

**From:** Ada \<ada@example.com>  
**To:** rene@example.com

Hello \<b>there\</b>

I ran tests locally under all features, the default build, --no-default-features, docx,spreadsheet,markdown,email and email alone. Clippy passes with -D warnings on each, and the OKF check passes.

Risk

  • Low: the feature is additive and opt-in; the default build is unchanged.
  • It depends on mail-parser 0.11 (default-features = false). 1.0 is a fresh rewrite, so it is not adopted yet.

Checklist

  • Tests added or updated: email_integration covers multipart, HTML-only, nested and detection, plus security tests for garbage input, header injection, deep multipart, attachment floods, input size and security_limits_apply (timeout, max_nodes, output cap)
  • Backward compatibility considered: the change is additive. Threat T7 and invariant S18 now cover email. Knowledge, CHANGELOG and README are updated.

Requested by Михайло · project thread


Generated by Claude Code

chaliy added 19 commits October 26, 2026 23:35
Plain text (always on) goes through the IR: paragraphs on blank lines,
hard breaks on single newlines. Markdown (new `markdown` feature, in
all_formats) is re-parsed with pulldown-cmark into the IR and rendered
back, so raw HTML never passes through. Both decode UTF-8/UTF-16 BOMs,
drop control characters and warn on lossy decoding. Neither is sniffed
from bytes. Threat model T7, invariant S13.
…-3z7tjs

# Conflicts:
#	CHANGELOG.md
#	README.md
#	crates/markkit/Cargo.toml
#	crates/markkit/src/extract.rs
#	crates/markkit/src/lib.rs
#	knowledge/log.md
#	knowledge/security/threat-model.md
…-3z7tjs

# Conflicts:
#	.github/workflows/ci.yml
#	crates/markkit/Cargo.toml
#	crates/markkit/src/extract.rs
#	crates/markkit/src/lib.rs
#	knowledge/log.md
#	knowledge/security/threat-model.md
…-3z7tjs

# Conflicts:
#	knowledge/log.md
#	knowledge/security/threat-model.md
Always-on RFC 4180 reader renders delimited text as one GFM table.
Delimiter is tab for TSV and detected among , ; tab | for CSV. Reading
is bounded by max_sheet_rows / max_sheet_cols (truncated_table warning);
unterminated quotes emit malformed_csv. Adds Format::Tsv.
…-3z7tjs

# Conflicts:
#	CHANGELOG.md
#	README.md
#	crates/markkit/src/extract.rs
#	knowledge/formats/format-support.md
#	knowledge/log.md
#	knowledge/security/threat-model.md
nbformat 4 notebooks render Markdown cells through the Markdown path,
code cells as fenced blocks tagged with the kernel language, and text
outputs and tracebacks (ANSI stripped) as text blocks; rich outputs are
skipped. Adds Format::Notebook, Error::NotebookExtract and
ExtractionStats::cells.
With no format feature, extract_document rendered unbounded, so always-on
text, CSV and notebook input ignored max_output_size and the timeout
(csv_integration::security_timeout_applies failed under
--no-default-features). Bounded rendering is now unconditional, decoding is
followed by a deadline check, and CI tests --no-default-features.
# Conflicts:
#	crates/markkit/src/extract.rs
#	crates/markkit/src/format.rs
#	crates/markkit/src/lib.rs
#	crates/markkit/src/limits.rs
#	crates/markkit/src/render/markdown.rs
#	knowledge/log.md
#	knowledge/operations/testing.md
New email feature (mail-parser 0.11) renders subject, From/To/Cc/Date,
the plain-text body (HTML alternative through the HTML parser when no
plain part exists) and an attachment list. Header values are folded so
encoded newlines cannot inject lines; attachments are never parsed.
Adds Format::Eml, Error::EmailExtract, ExtractionStats::attachments.
@chaliy chaliy self-assigned this Oct 27, 2026
…-3z7tjs

# Conflicts:
#	crates/markkit-js/src/lib.rs
#	crates/markkit/src/extract.rs
#	knowledge/formats/format-support.md
@chaliy
chaliy merged commit 9a138c0 into main Oct 27, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant