TypeError for SafeMIMEText
Stranger6667 opened this issue · 9 comments
Follow up for this comment:
@Stranger6667 Is this working for you? I am getting the same exception with Python 2.7, Django 1.11 as well as Python 3.6, Django 1.11.
I get
TypeError: Object of type 'SafeMIMEText' is not JSON serializable
and I am unable to debug the issue.
Hi @suriya !
Could you, please, add some code to reproduce the issue?
Please take a look at this Github gist.
I am reproducing the Python code here. Run the code with Django version 1.11 and Python 3.6. I think the behavior should be the same for other versions as well.
The summary is that Postmarker crashes when sending an email with a HTML alternative as well as an attachment. Sending a mail with a HTML alternative alone works. Send a mail with an attachment alone works.
There is also a different issue. Sending a text/plain
attachment also causes an error. It looks like Postmarker is not encoding the attachment body properly.
from django.conf import settings
settings.configure(
DEBUG=True,
SECRET_KEY='A-random-secret-key!',
EMAIL_BACKEND = 'postmarker.django.EmailBackend',
POSTMARK = {
'TOKEN': 'POSTMARK_API_TEST',
'TEST_MODE': True,
'VERBOSITY': 3,
}
)
def text_and_html_alternative_success():
"""
Thus function sends a text body and html alternative.
This function succeeds.
"""
from django.core.mail import EmailMultiAlternatives
msg = EmailMultiAlternatives(
subject='Subject', body='Text body', from_email='from@example.com',
to=['to@example.com']
)
msg.attach_alternative('<html></html>', "text/html")
msg.send(fail_silently=False)
def text_and_pdf_attachment_success():
"""
This functions sends a text body and PDF attachment
This function succeeds.
"""
from django.core.mail import EmailMultiAlternatives
msg = EmailMultiAlternatives(
subject='Subject', body='Body', from_email='from@example.com',
to=['to@example.com']
)
msg.attach('hello.pdf', 'PDF-File-Contents', 'application/pdf')
msg.send(fail_silently=False)
def text_html_alternative_and_pdf_attachment_failure():
"""
This functions sends a text body, HTML alternative, and PDF attachment.
This function fails.
"""
from django.core.mail import EmailMultiAlternatives
msg = EmailMultiAlternatives(
subject='Subject', body='Body', from_email='from@example.com',
to=['to@example.com']
)
msg.attach_alternative('<html></html>', "text/html")
msg.attach('hello.pdf', 'PDF-File-Contents', 'application/pdf')
msg.send(fail_silently=False)
def text_and_text_attachment_failure():
"""
This function sends a text body and text attachment.
This function fails.
"""
from django.core.mail import EmailMultiAlternatives
msg = EmailMultiAlternatives(
subject='Subject', body='Body', from_email='from@example.com',
to=['to@example.com']
)
msg.attach('hello.txt', 'Hello World', 'text/plain')
msg.send(fail_silently=False)
if __name__ == '__main__':
import django
django.setup()
# text_and_html_alternative_success()
# text_and_pdf_attachment_success()
# text_html_alternative_and_pdf_attachment_failure()
# text_and_text_attachment_failure()
Regarding plain text attachment. You have to pass base64 encoded content:
From the docs
Note! Content should be encoded as Base64 string. Then pass the attachments to send()
Regarding the second issue - I'll take a look tomorrow
Thank you for reporting the problem :)
Probably, a validation for base64 string could be added. What do you think?
The issue is in deconstruct_multipart
postmarker/postmarker/models/emails.py
Line 80 in 62cc043
I am a bit confused. Is the base64 encoding needed only for text/plain or for other types as well? I have sent PDF attachments by providing the raw content without any encoding.
Also, the base64 encoding could be a requirement for a low level API. However, it should not be required when sending emails from Django. (I don't need to send text attachments. I came across this issue while debugging the other TypeError issue). When sending from Django, the API to the user should be unchanged. The user should simply have to change their backend and the code should work. Any base-64 encoding should be performed by the postmarker library and not by the user.
The following patch seems to work for me. I don't know enough details to know if I am doing the right thing.
diff --git a/postmarker/models/emails.py b/postmarker/models/emails.py
index bb8a4c7..c629d95 100644
--- a/postmarker/models/emails.py
+++ b/postmarker/models/emails.py
@@ -76,21 +76,29 @@ def prepare_attachments(attachment):
result = attachment
return result
-
-def deconstruct_multipart(message):
- text, html, attachments = None, None, []
- for part in message.walk():
- if part is message:
- continue
- content_type = part.get_content_type()
- if content_type == 'text/plain' and text is None:
- text = part.get_payload(decode=True).decode('utf8')
- elif content_type == 'text/html' and html is None:
- html = part.get_payload(decode=True).decode('utf8')
+def deconstruct_multipart_recursive(seen, text, html, attachments, message):
+ if (message in seen):
+ return
+ seen.add(message)
+ if isinstance(message, MIMEMultipart):
+ for part in message.walk():
+ deconstruct_multipart_recursive(seen, text, html, attachments, part)
+ else:
+ content_type = message.get_content_type()
+ if content_type == 'text/plain' and not text:
+ text.append(message.get_payload(decode=True).decode('utf8'))
+ elif content_type == 'text/html' and not html:
+ html.append(message.get_payload(decode=True).decode('utf8'))
else:
- attachments.append(part)
- return text, html, attachments
+ attachments.append(message)
+def deconstruct_multipart(message):
+ seen = set()
+ text = []
+ html = []
+ attachments = []
+ deconstruct_multipart_recursive(seen, text, html, attachments, message)
+ return ((text and text[0]) or None, (html and html[0]) or None, attachments)
class BaseEmail(Model):
@@ -178,10 +186,7 @@ class Email(BaseEmail):
:param manager: :py:class:`EmailManager` instance.
:return: :py:class:`Email`
"""
- if isinstance(message, MIMEMultipart):
- text, html, attachments = deconstruct_multipart(message)
- else:
- text, html, attachments = message.get_payload(decode=True).decode('utf8'), None, []
+ text, html, attachments = deconstruct_multipart(message)
subject = prepare_header(message['Subject'])
sender = prepare_header(message['From'])
to = prepare_header(message['To'])
Btw, regarding base64 - Django automatically encodes content in base64, that's why it was possible to send pdf without an error.
I'll make a change in postmarker to automatically encode data in base64 in similar way, but, probably it will be controlled by some kwarg, will see.
I am a bit confused. Is the base64 encoding needed only for text/plain or for other types as well?
I don't know yet :(
I'll create a separate issue for base64 encoding