【问题标题】:send a DSN (Non-delivery report) using the python email module使用 python 电子邮件模块发送 DSN(未送达报告)
【发布时间】:2023-02-15 21:27:51
【问题描述】:

我正在尝试使用电子邮件模块从 python3 发送 DSN(传递状态通知,也称为未传递报告)。

DSN 是带有 Content-Type: multipart/report; report-type=delivery-status 的 MIME 消息

消息包含 2 个附件(和可选的第 3 个附件):

  1. content-type: text/plain一份人类可读的报告
  2. content-type: message/delivery-status一份机器可读的报告
  3. content-type: message/rfc822 可选的原始消息
    #
    # Get data from msg
    #
    headers = Parser(policy=default).parsestr(msg)
    recipient = headers['to'].addresses[0].addr_spec
    domain = headers['to'].addresses[0].domain
    date = email.utils.formatdate(localtime=True)
    
    #
    # Create a new email message
    #
    dsn = EmailMessage()
    dsn.policy = policy.SMTP  # <-- this didn't help
    dsn.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: failed
    Status: 5.7.1
    Diagnostic-Code: smtp; 571 Delivery not authorized, message returned
    Last-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 here
    

    打印 DSN 时,我收到以下回溯:

    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.policy
    AttributeError: 'str' object has no attribute 'policy'
    

    问题似乎是第二个附件的主要类型和子类型,content-type: message/delivery-status 附件。如果我将其更改为 text/plain,则 DSN 会打印出第二个附件的内容类型错误。

    1. 这是使用此模块构建 DSN 的正确方法吗?
    2. 如何修复此策略属性问题?

【问题讨论】:

  • 我试着玩弄这段代码,但我真的弄不明白。您似乎在附件代码中发现了错误。也许您只需要生成 msg.as_string() 然后用内容类型替换其中的传递报告,尽管这是一个糟糕的解决方法。

标签: python python-3.x email


【解决方案1】:

看起来 message 主要类型的处理不完整或有问题。

一个复杂的问题是 set_contentadd_attachment 的行为不同,具体取决于您附加的是 str 还是 bytes 对象。我想您已经明白了这一点,但它可能有点令人费解。

您代码中的 cte=None 似乎是出于类似观察的动机。如果你输入 cte="7bit",Python 将再次尝试将该对象转换为字符串,如果它已经是一个 bytes 对象,则不能。

我能想到的最好办法是在呈现消息后用蛮力修饰来替换内容类型。

import email.utils
from email.message import EmailMessage
from email.parser import Parser
from email.policy import default

msg = """
Return-Path: <sender@example.org>
From: me <sender@example.org>
To: recipient <victim@example.net>
Subject: Deliver this

Hello
"""

#
# Get data from msg
#
headers = Parser(policy=default).parsestr(msg)
recipient = headers['to'].addresses[0].addr_spec
domain = headers['to'].addresses[0].domain
date = email.utils.formatdate(localtime=True)

#
# Create a new email message
#
dsn = EmailMessage()
# dsn.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.set_content("""
   ----- The following address had delivery problems -----
   <{}> (unrecoverable error: Refused)
""".format(recipient))

#
# The machine readable part
#
dsn.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 returned
Last-Attempt-Date: {}
""".format(domain, recipient, recipient, date).encode(),
                   maintype="application",
                   subtype="x-tripleeee-message-delivery-status",
                   cte="8bit")

#
# The original message
#
dsn.add_attachment(msg.encode(),
                   maintype="message",
                   subtype="rfc822",
                   cte="8bit")

#
# Set the Content-Type header in the message headers
#
dsn.replace_header('Content-Type', 'multipart/report')
dsn.set_param('report-type', 'delivery-status')

#
# Touch up CTEs
#
parts = list(dsn.iter_attachments())
parts[-1].replace_header("Content-Transfer-Encoding", "7bit")
parts[-2].replace_header("Content-Transfer-Encoding", "7bit")

#
# Now here's the really fugly part
#

# parts[-2].replace_header("Content-Type", "message/delivery-status")
as_bytes = dsn.as_bytes().replace(
    b"application/x-tripleeee-message-delivery-status",
    b"message/delivery-status")

print(as_bytes.decode())

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-08
    • 2021-06-17
    • 1970-01-01
    • 2012-05-07
    • 2019-04-17
    • 1970-01-01
    • 2013-02-18
    • 1970-01-01
    相关资源
    最近更新 更多