Help creating a Delivery Status Notification using email library module

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?

Not to detract, but if you can use Sendgrid - I would use it. It has a python library too.

Sendgrid? as in the paid service to send email? I’m completely confused as to why this might be helpful. I’m developing something in Python, it’s sending and receiving emails.

Sendgrid has a free tier. Sendgrid is just a very streamlined solution for sending email via Python and PowerShell. They have a web API and SMTP servers.

I guess I didn’t realize you needed to receive them.

You may check Github Issues page if there is one.