From e88303cc161aebd31f13c2a9e5298192b606d1cb Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 5 Nov 2026 18:03:36 -0800 Subject: [PATCH] Fix: apply PDF/A conversion once for mail archives (#14698) --- src/paperless/parsers/mail.py | 36 +++++++++++++------ .../tests/parsers/test_mail_parser.py | 31 ++++++++++++++++ 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/paperless/parsers/mail.py b/src/paperless/parsers/mail.py index 7fac9c669a..1b96982ad6 100644 --- a/src/paperless/parsers/mail.py +++ b/src/paperless/parsers/mail.py @@ -554,7 +554,11 @@ class MailDocumentParser: """ archive_path = Path(self._tempdir) / "merged.pdf" - mail_pdf_file = self.generate_pdf_from_mail(mail_message) + # Apply PDF/A conversion once, when producing the final archive. + mail_pdf_file = self.generate_pdf_from_mail( + mail_message, + convert_to_pdfa=not mail_message.html, + ) if pdf_layout is None: pdf_layout = MailRule.PdfLayout(settings.EMAIL_PARSE_DEFAULT_LAYOUT) @@ -567,6 +571,7 @@ class MailDocumentParser: pdf_of_html_content = self.generate_pdf_from_html( mail_message.html, mail_message.attachments, + convert_to_pdfa=False, ) logger.debug("Merging email text and HTML content into single PDF") @@ -672,13 +677,20 @@ class MailDocumentParser: return html_file - def generate_pdf_from_mail(self, mail: MailMessage) -> Path: + def generate_pdf_from_mail( + self, + mail: MailMessage, + *, + convert_to_pdfa: bool = True, + ) -> Path: """Create a PDF from the email body using an HTML template and Gotenberg. Parameters ---------- mail: Parsed mail message. + convert_to_pdfa: + Apply the configured PDF/A format to this output. Returns ------- @@ -707,10 +719,10 @@ class MailDocumentParser: ) as client, client.chromium.html_to_pdf() as route, ): - # Configure requested PDF/A formatting, if any - pdf_a_format = self._settings_to_gotenberg_pdfa() - if pdf_a_format is not None: - route.pdf_format(pdf_a_format) + if convert_to_pdfa: + pdf_a_format = self._settings_to_gotenberg_pdfa() + if pdf_a_format is not None: + route.pdf_format(pdf_a_format) try: response = ( @@ -742,6 +754,8 @@ class MailDocumentParser: self, orig_html: str, attachments: list[MailAttachment], + *, + convert_to_pdfa: bool = True, ) -> Path: """Generate a PDF from the HTML content of the email. @@ -751,6 +765,8 @@ class MailDocumentParser: Raw HTML string from the email body. attachments: List of email attachments (used as inline resources). + convert_to_pdfa: + Apply the configured PDF/A format to this output. Returns ------- @@ -786,10 +802,10 @@ class MailDocumentParser: ) as client, client.chromium.html_to_pdf() as route, ): - # Configure requested PDF/A formatting, if any - pdf_a_format = self._settings_to_gotenberg_pdfa() - if pdf_a_format is not None: - route.pdf_format(pdf_a_format) + if convert_to_pdfa: + pdf_a_format = self._settings_to_gotenberg_pdfa() + if pdf_a_format is not None: + route.pdf_format(pdf_a_format) # Add attachments as resources, cleaning the filename and replacing # it in the index file for inclusion diff --git a/src/paperless/tests/parsers/test_mail_parser.py b/src/paperless/tests/parsers/test_mail_parser.py index 7586067287..2e14e52dbe 100644 --- a/src/paperless/tests/parsers/test_mail_parser.py +++ b/src/paperless/tests/parsers/test_mail_parser.py @@ -594,6 +594,23 @@ class TestParser: assert mail_parser.get_archive_path() is not None + def test_generate_pdf_from_html_with_pdfa( + self, + httpx_mock: HTTPXMock, + mail_parser: MailDocumentParser, + html_email_pdf_file: Path, + ) -> None: + url = "http://localhost:3000/forms/chromium/convert/html" + pdf_content = html_email_pdf_file.read_bytes() + httpx_mock.add_response(url=url, method="POST", content=pdf_content) + + archive = mail_parser.generate_pdf_from_html("
Email body
", []) + + assert archive.read_bytes() == pdf_content + request = httpx_mock.get_request(url=url) + assert request is not None + assert b'name="pdfa"\r\n\r\nPDF/A-2b\r\n' in request.content + @pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_generate_pdf_html_email( self, @@ -635,6 +652,20 @@ class TestParser: assert mail_parser.get_archive_path() is not None + chromium_requests = httpx_mock.get_requests( + url="http://localhost:3000/forms/chromium/convert/html", + ) + assert len(chromium_requests) == 2 + assert all( + b'name="pdfa"' not in request.content for request in chromium_requests + ) + + merge_request = httpx_mock.get_request( + url="http://localhost:3000/forms/pdfengines/merge", + ) + assert merge_request is not None + assert b'name="pdfa"\r\n\r\nPDF/A-2b\r\n' in merge_request.content + def test_generate_pdf_html_email_html_to_pdf_failure( self, httpx_mock: HTTPXMock,