From 507aa4c9c48a203370f6e34190b06935293f4119 Mon Sep 17 00:00:00 2001 From: Andrew DeOrio Date: Wed, 14 Jan 2026 09:30:23 -0500 Subject: [PATCH 1/3] Bump Claude allowed commands --- .claude/settings.local.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 10200b8..05d60c8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,8 @@ "Bash(pip show:*)", "Bash(echo:*)", "Bash(tox)", - "Bash(tox:*)" + "Bash(tox:*)", + "Bash(gh issue view:*)" ] } } From fc8a36fb855e612b421848809824ceb6d4d34cba Mon Sep 17 00:00:00 2001 From: Andrew DeOrio Date: Wed, 14 Jan 2026 09:30:52 -0500 Subject: [PATCH 2/3] Add test exposing bug --- tests/test_template_message.py | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_template_message.py b/tests/test_template_message.py index cd12a54..2aa4e8a 100644 --- a/tests/test_template_message.py +++ b/tests/test_template_message.py @@ -865,3 +865,61 @@ def test_content_id_header_for_attachments(tmpdir): assert filename == "attachment.txt" assert content == b"Hello world\n" assert re.match(r'<[\d\w]+(\.[\d\w]+)*@mailmerge\.invalid>', cid_header) + + +def test_svg_with_attachment(tmp_path): + """SVG elements should not have namespace prefixes when attachments exist. + + Regression test for https://github.com/awdeorio/mailmerge/issues/165 + """ + # Create a dummy attachment + attachment_path = tmp_path / "image.png" + attachment_path.write_bytes(b"fake png data") + + # Create template with inline SVG and an attachment referencing the image + template_path = tmp_path / "template.txt" + template_path.write_text(textwrap.dedent("""\ + TO: to@test.com + FROM: from@test.com + SUBJECT: SVG Test + ATTACHMENT: image.png + Content-Type: text/html + + + + + + + test + + + """), encoding="utf8") + + # Render template + template_message = TemplateMessage(template_path) + _, _, message = template_message.render({}) + + # Get the HTML part + assert message.is_multipart() + html_part = None + for part in message.walk(): + if part.get_content_type() == "text/html": + html_part = part + break + assert html_part is not None + + # Get HTML content + html_content = html_part.get_payload(decode=True).decode("utf-8") + + # Verify SVG elements do NOT have namespace prefixes + assert "ns0:svg" not in html_content, \ + "SVG element should not have namespace prefix" + assert "ns0:circle" not in html_content, \ + "circle element should not have namespace prefix" + + # Verify SVG elements are present (without prefixes) + assert " Date: Wed, 14 Jan 2026 09:32:38 -0500 Subject: [PATCH 3/3] Use html5lib for serialization --- mailmerge/template_message.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mailmerge/template_message.py b/mailmerge/template_message.py index baafd4e..de8a4bf 100644 --- a/mailmerge/template_message.py +++ b/mailmerge/template_message.py @@ -6,7 +6,6 @@ import re from pathlib import Path -from xml.etree import ElementTree import email import email.mime import email.mime.application @@ -294,7 +293,12 @@ def _transform_attachment_references(self): # We only need to update the message if we cleared the header, # which only happens if we transformed an attachment reference. if 'Content-Transfer-Encoding' not in part: - new_html = ElementTree.tostring(document).decode('utf-8') + new_html = html5lib.serialize( + document, + tree='etree', + omit_optional_tags=False, + quote_attr_values='always', + ) part.set_payload(new_html) def _resolve_attachment_path(self, path):