I’m trying to create a DSN (also known as a Non-Delivery Report) using the email library module.
I create a new message and add 3 attachments. When I try to print the resulting email, I get this error:
Traceback (most recent call last):
File "/home/mgrant/./send-dsn.py", line 97, in <module>
print(dsn)
File "/usr/lib/python3.9/email/message.py", line 971, in __str__
return self.as_string(policy=self.policy.clone(utf8=True))
File "/usr/lib/python3.9/email/message.py", line 968, in as_string
return super().as_string(unixfrom, maxheaderlen, policy)
File "/usr/lib/python3.9/email/message.py", line 158, in as_string
g.flatten(self, unixfrom=unixfrom)
File "/usr/lib/python3.9/email/generator.py", line 116, in flatten
self._write(msg)
File "/usr/lib/python3.9/email/generator.py", line 181, in _write
self._dispatch(msg)
File "/usr/lib/python3.9/email/generator.py", line 218, in _dispatch
meth(msg)
File "/usr/lib/python3.9/email/generator.py", line 276, in _handle_multipart
g.flatten(part, unixfrom=False, linesep=self._NL)
File "/usr/lib/python3.9/email/generator.py", line 116, in flatten
self._write(msg)
File "/usr/lib/python3.9/email/generator.py", line 181, in _write
self._dispatch(msg)
File "/usr/lib/python3.9/email/generator.py", line 218, in _dispatch
meth(msg)
File "/usr/lib/python3.9/email/generator.py", line 335, in _handle_message_delivery_status
g.flatten(part, unixfrom=False, linesep=self._NL)
File "/usr/lib/python3.9/email/generator.py", line 107, in flatten
old_msg_policy = msg.policy
AttributeError: 'str' object has no attribute 'policy'
I’ve narrowed this down to the second attachment which I create as follows:
msg.add_attachment("""\
Reporting-MTA: dns; {}
Original-Recipient: rfc822;{}
Final-Recipient: rfc822;{}
Action: failed
Status: 5.7.1
Diagnostic-Code: smtp; 571 Delivery not authorized, message refused
Last-Attempt-Date: {}
""".format(domain, recipient, recipient, received).encode('us-ascii'),
maintype="message",
subtype="delivery-status",
cte=None)
If I change the maintype to “text” and subtype to “plain”, the message prints out, but with the incorrect content-type for that part. The message/delivery-status
causes it to call _handle_message_delivery_status()
in generator.py. I’m starting to wonder if there’s a bug in there, but equally, I’m not convinced I’m doing this correctly.
In _handle_message_delivery_status()
in generator.py, there is this loop:
for part in msg.get_payload():
s = self._new_buffer()
g = self.clone(s)
=> g.flatten(part, unixfrom=False, linesep=self._NL)
...
msg.get_payload()
is returning the entire string which I formatted in my attachment above, for example:
(Pdb) msg.get_payload()
'Reporting-MTA: dns; example.com\n\nOriginal-Recipient: rfc822;joe.user@example.com\nFinal-Recipient: rfc822;joe.user@example.com\nAction: failed\nStatus: 5.7.1\nDiagnostic-Code: smtp; 571 Delivery not authorized, message refused\nLast-Attempt-Date: Thu, 05 May 2022 18:54:51 -0000\n'
Hence the for loop, part
gets each character of this in form of a string starting with ‘R’, clearly this isn’t what that loop is expecting!
Is this a bug in _handle_message_delivery_status()
? or am I not creating this body part attachment correctly?