Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mailjet: Prevent empty attachment filename #410

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ Breaking changes
(Postal's signature verification uses the "cryptography" package, which is no
longer reliably installable with Python 3.8.)

Fixes
~~~~~

* **Mailjet:** Avoid a Mailjet API error when sending an inline image without a
filename. (Anymail now substitutes ``"attachment"`` for the missing filename.)
(Thanks to `@chickahoona`_ for reporting the issue.)


v12.0
-----
Expand Down Expand Up @@ -1737,6 +1744,7 @@ Features
.. _@b0d0nne11: https://github.com/b0d0nne11
.. _@calvin: https://github.com/calvin
.. _@carrerasrodrigo: https://github.com/carrerasrodrigo
.. _@chickahoona: https://github.com/chickahoona
.. _@chrisgrande: https://github.com/chrisgrande
.. _@cjsoftuk: https://github.com/cjsoftuk
.. _@costela: https://github.com/costela
Expand Down
3 changes: 2 additions & 1 deletion anymail/backends/mailjet.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def set_html_body(self, body):
def add_attachment(self, attachment):
att = {
"ContentType": attachment.mimetype,
"Filename": attachment.name or "",
# Mailjet requires a non-empty Filename.
"Filename": attachment.name or "attachment",
"Base64Content": attachment.b64content,
}
if attachment.inline:
Expand Down
9 changes: 9 additions & 0 deletions docs/esps/mailjet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ Limitations and quirks
:ref:`esp-send-status`, because Mailjet's other (statistics, event tracking)
APIs don't yet support MessageUUID.

**Attachments require filenames**
Mailjet requires that all attachments and inline images have filenames. If you
don't supply a filename, Anymail will use ``"attachment"`` as the filename.

.. versionchanged:: 13.0

Earlier Anymail versions would default to an empty string, resulting in
a Mailjet API error.

**Older limitations**

.. versionchanged:: 6.0
Expand Down
14 changes: 10 additions & 4 deletions tests/test_mailjet_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
AnymailSerializationError,
AnymailUnsupportedFeature,
)
from anymail.message import attach_inline_image_file
from anymail.message import attach_inline_image, attach_inline_image_file

from .mock_requests_backend import (
RequestsBackendMockAPITestCase,
Expand Down Expand Up @@ -266,7 +266,7 @@ def test_attachments(self):
self.assertNotIn("ContentID", attachments[1])

self.assertEqual(attachments[2]["ContentType"], "application/pdf")
self.assertEqual(attachments[2]["Filename"], "") # none
self.assertEqual(attachments[2]["Filename"], "attachment")
self.assertEqual(decode_att(attachments[2]["Base64Content"]), pdf_content)
self.assertNotIn("ContentID", attachments[2])

Expand Down Expand Up @@ -297,6 +297,7 @@ def test_embedded_images(self):
image_data = sample_image_content(image_filename)

cid = attach_inline_image_file(self.message, image_path) # Read from a png file
cid2 = attach_inline_image(self.message, image_data)
html_content = (
'<p>This has an <img src="cid:%s" alt="inline" /> image.</p>' % cid
)
Expand All @@ -307,11 +308,16 @@ def test_embedded_images(self):
self.assertEqual(data["Globals"]["HTMLPart"], html_content)

attachments = data["Globals"]["InlinedAttachments"]
self.assertEqual(len(attachments), 1)
self.assertEqual(len(attachments), 2)
self.assertEqual(attachments[0]["Filename"], image_filename)
self.assertEqual(attachments[0]["ContentID"], cid)
self.assertEqual(attachments[0]["ContentType"], "image/png")
self.assertEqual(decode_att(attachments[0]["Base64Content"]), image_data)
# Mailjet requires a filename for all attachments, so make sure it's not empty:
self.assertEqual(attachments[1]["Filename"], "attachment")
self.assertEqual(attachments[1]["ContentID"], cid2)
self.assertEqual(attachments[1]["ContentType"], "image/png")
self.assertEqual(decode_att(attachments[1]["Base64Content"]), image_data)

self.assertNotIn("Attachments", data["Globals"])

Expand Down Expand Up @@ -340,7 +346,7 @@ def test_attached_images(self):
"Base64Content": image_data_b64,
},
{
"Filename": "", # the unnamed one
"Filename": "attachment", # the unnamed one
"ContentType": "image/png",
"Base64Content": image_data_b64,
},
Expand Down
4 changes: 2 additions & 2 deletions tests/test_mailjet_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage

from .utils import AnymailTestMixin, sample_image_path
from .utils import AnymailTestMixin, sample_image_content

ANYMAIL_TEST_MAILJET_API_KEY = os.getenv("ANYMAIL_TEST_MAILJET_API_KEY")
ANYMAIL_TEST_MAILJET_SECRET_KEY = os.getenv("ANYMAIL_TEST_MAILJET_SECRET_KEY")
Expand Down Expand Up @@ -91,7 +91,7 @@ def test_all_options(self):
)
message.attach("attachment1.txt", "Here is some\ntext for you", "text/plain")
message.attach("attachment2.csv", "ID,Name\n1,Amy Lina", "text/csv")
cid = message.attach_inline_image_file(sample_image_path())
cid = message.attach_inline_image(sample_image_content())
message.attach_alternative(
"<p><b>HTML:</b> with <a href='http://example.com'>link</a>"
"and image: <img src='cid:%s'></div>" % cid,
Expand Down
Loading