I'm trying to send a DSN (Delivery Status Notification, also known as a Non-Delivery Report) from python3 using the email module.
A DSN is a MIME message with Content-Type: multipart/report; report-type=delivery-status
The messages contain 2 attachments (and an optional 3rd):
content-type: text/plaina human readable reportcontent-type: message/delivery-statusa machine readable reportcontent-type: message/rfc822optionally the original message
## Get data from msg#headers = Parser(policy=default).parsestr(msg)recipient = headers['to'].addresses[0].addr_specdomain = headers['to'].addresses[0].domaindate = email.utils.formatdate(localtime=True)## Create a new email message#dsn = EmailMessage()dsn.policy = policy.SMTP # <-- this didn't helpdsn.make_mixed()dsn['From'] = headers['to']dsn['Date'] = email.utils.localtime(dt=None)dsn['Message-Id'] = email.utils.make_msgid(idstring=None, domain=None)dsn['Subject'] = 'Returned Mail: Refused'dsn['To'] = headers['return-path']## The human readable part#dsn.add_attachment("""\ ----- The following address had delivery problems -----<{}> (unrecoverable error: Refused)""".format(recipient).encode(), maintype="text", subtype="plain", cte=None)## The machine readable part#dsn.add_attachment("""\Reporting-MTA: dns; {}Original-Recipient: rfc822;{}Final-Recipient: rfc822;{}Action: failedStatus: 5.7.1Diagnostic-Code: smtp; 571 Delivery not authorized, message returnedLast-Attempt-Date: {}""".format(domain, recipient, recipient, date).encode('us-ascii'), maintype="message", # <--- these 2 lines cause subtype="delivery-status", # <--- the issue cte=None)## The original message#dsn.add_attachment(msg.encode(), maintype="message", subtype="rfc822", cte=None)## Set the Content-Type header in the message headers#dsn.replace_header('Content-Type', 'multipart/report')dsn.set_param('report-type', 'delivery-status')print(dsn) # <--- Dies in hereWhen the DSN is printed, I receive the following traceback:
Traceback (most recent call last): File "./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.policyAttributeError: 'str' object has no attribute 'policy'The problem seems to be the maintype and subtype of the second attachment, the content-type: message/delivery-status attachment. If I change this to text/plain, the DSN prints except that the second attachment has the wrong content-type.
- Is this the correct way to build a DSN using this module?
- How can I fix this policy attribute problem?