docling-project / docling-project/docling
Email attachment processing: reuse `DocumentConverter` to parse attachments
- Dominant language
- Python
- Stars
- 66.4k
- Forks
- 4.8k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 84
Description
## Context
Hi maintainers, this issue is for the design discussion of the next step in the Outlook `.msg` support PR #3873.
Thank you Dr @dolfim-ibm for your guidance in this!
Email attachments are frequently real documents (PDF, DOCX, XLSX, …) whose content would be valuable to extract, not just list.
Opening this to agree on the approach before I implement it.
## Current state (after the `.msg`/`.eml` PR)
`EmailDocumentBackend` parses the message (subject, from/to, date, plain + HTML body). When `EmailBackendOptions(list_attachments=True)` (opt-in, default off) it appends an **Attachments** section listing each attachment as `filename (content_type)`. Attachment **payloads are intentionally left untouched** — that's what this issue proposes to add.
The decoded attachment bytes are already reachable on both paths: the `.msg` is projected to RFC 822 and parsed by `mailparser`, so `self.mail.attachments[i]` carries the payload for `.eml` and `.msg` alike.
## Proposal: reuse `DocumentConverter`
When enabled, feed each attachment's bytes back through Docling's own `DocumentConverter`. It already detects the format from the filename/content and runs that format's normal pipeline (PDF → PDF pipeline, DOCX → Word backend, etc.), so the email backend needs **no per-format logic**. The converted result is merged into the email document under a per-attachment heading:
```python
# lazily imported inside the method to avoid an import cycle
from docling.document_converter import DocumentConverter
from docling_core.types.io import DocumentStream
stream = DocumentStream(name=filename, stream=BytesIO(payload_bytes))
child = self._attachment_converter.convert(stream).document
section = doc.add_heading(text=filename, level=2)
doc.add_document(child, parent=section) # existing docling_core merge primitive
```
Each email stays a single `DoclingDocument` (fits the declarative-backend
contract); attachments become sub-sections under their filenames.
## Implementation notes (already accounted for)
- **Lazy import**: `document_converter.py` imports `EmailDocumentBackend` at module top, so `DocumentConverter` is imported inside the method to avoid a circular import.
- **One converter per email, reused**: pipelines are cached per `DocumentConverter` instance, so the backend builds one converter and reuses it for all attachments rather than paying pipeline init per file.
- **Per-attachment error isolation**: each conversion is wrapped in try/except; an unsupported type, a missing format extra (slim installs), or a corrupt file falls back to listing that attachment's `filename (content_type)`, never failing the whole email.
- **Recursion is naturally bounded**: the internal converter uses *default* email options (`process_attachments=False`), so an attached `.eml`/`.msg` has its body parsed but its own attachments are only listed, not recursed. A `max_attachment_depth` cap is still added as a safety belt.
## Proposed `EmailBackendOptions` extension
```python
process_attachments: bool = False # opt-in master switch
attachment_formats: set[InputFormat] | None = None # None -> default allow-list
max_attachments: int = 32
max_attachment_bytes: int = 25 * 1024 * 1024 # per attachment
max_attachment_total_bytes: int = 100 * 1024 * 1024 # cumulative (zip-bomb guard)
max_attachment_depth: int = 1 # nested emails / .msg
```
Limits mirror the existing `EpubBackendOptions` / `MetsGbsBackendOptions` patterns for untrusted archive input.
## What I'd like maintainer input on
1. **Default format allow-list.** Proposed: `PDF, DOCX, PPTX, XLSX, HTML, MD, CSV, IMAGE`, plus nested `EMAIL`. Everything else falls back to listing. Convert all supported formats instead, or trim this?
2. **Limit defaults.** Are the counts/sizes above reasonable starting points?
3. **Partial failures.** If the body converts but some attachments fail, should the result report `ConversionStatus.PARTIAL_SUCCESS`, or succeed with a logged warning?
I'm happy to implement this once we've finalized #3873 and agree on this design! Thanks again
Contributor guide
Research direction
Start by reading the EmailDocumentBackend and EmailBackendOptions implementation, then review document_converter.py and the linked Outlook support PR #3873. Trace how attachment payloads are exposed and how DocumentStream and document merging are used. Done means the allow-list, resource limits, recursion behavior, and partial-failure policy are agreed before implementation begins.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100