diff --git a/Doc/library/smtplib.rst b/Doc/library/smtplib.rst index 5c97199bc453e8..d34d6a2062345a 100644 --- a/Doc/library/smtplib.rst +++ b/Doc/library/smtplib.rst @@ -534,12 +534,17 @@ An :class:`SMTP` instance has the following methods: specified in :rfc:`5322`\: *from_addr* is set to the :mailheader:`Sender` field if it is present, and otherwise to the :mailheader:`From` field. *to_addrs* combines the values (if any) of the :mailheader:`To`, - :mailheader:`Cc`, and :mailheader:`Bcc` fields from *msg*. If exactly one - set of :mailheader:`Resent-*` headers appear in the message, the regular - headers are ignored and the :mailheader:`Resent-*` headers are used instead. - If the message contains more than one set of :mailheader:`Resent-*` headers, - a :exc:`ValueError` is raised, since there is no way to unambiguously detect - the most recent set of :mailheader:`Resent-` headers. + :mailheader:`Cc`, and :mailheader:`Bcc` fields from *msg*. If there's no + :mailheader:`Date` header inside the message, ``send_message`` will add one to the data. + If exactly one set of :mailheader:`Resent-*` headers appear in the message, + the regular headers are ignored and the :mailheader:`Resent-*` headers are + used instead. If the message contains more than one set of + :mailheader:`Resent-*` headers, a :exc:`ValueError` is raised, since there + is no way to unambiguously detect the most recent set of + :mailheader:`Resent-` headers. + + .. versionchanged:: next + Support to add :mailheader:`Date` header to the message if one does not exist. ``send_message`` serializes *msg* using :class:`~email.generator.BytesGenerator` with ``\r\n`` as the *linesep*, and diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 4cfc2338d99c67..5a62b271566a21 100644 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -949,6 +949,11 @@ def send_message(self, msg, from_addr=None, to_addrs=None, header_prefix = 'Resent-' else: raise ValueError("message has more than one 'Resent-' header block") + + # RFC 5322 section 3.6, 4th Paragraph + if 'Date' not in msg: + # localtime: RFC 5322 section 3.3, 4th Paragraph + msg['Date'] = email.utils.formatdate(localtime=True) if from_addr is None: # Prefer the sender field per RFC 5322 section 3.6.2. from_addr = (msg[header_prefix + 'Sender'] diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index b8aac8c20202a2..e4fdd236cc5f79 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -1504,6 +1504,7 @@ def test_send_unicode_with_SMTPUTF8_via_low_level_API(self): self.assertIn('SMTPUTF8', self.serv.last_mail_options) self.assertEqual(self.serv.last_rcpt_options, []) + @support.run_with_tz('UTC-02') def test_send_message_uses_smtputf8_if_addrs_non_ascii(self): msg = EmailMessage() msg['From'] = "Páolo " @@ -1514,24 +1515,35 @@ def test_send_message_uses_smtputf8_if_addrs_non_ascii(self): msg.set_content("oh là là, know what I mean, know what I mean?\n\n") # XXX smtpd converts received /r/n to /n, so we can't easily test that # we are successfully sending /r/n :(. - expected = textwrap.dedent("""\ + smtp = smtplib.SMTP( + HOST, self.port, local_hostname='localhost', + timeout=support.LOOPBACK_TIMEOUT) + self.addCleanup(smtp.close) + self.assertEqual(smtp.send_message(msg), {}) + + last_message = self.serv.last_message.decode() + date = email.message_from_string(last_message)['Date'] + # asserts RFC 5322 section 3.3 4th Paragraph + self.assertEqual( + email.utils.parsedate_to_datetime(date).tzname(), + "UTC+02:00" + ) + + expected = textwrap.dedent(f"""\ From: Páolo To: Dinsdale Subject: Nudge nudge, wink, wink \u1F609 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit MIME-Version: 1.0 + Date: {date} oh là là, know what I mean, know what I mean? """) - smtp = smtplib.SMTP( - HOST, self.port, local_hostname='localhost', - timeout=support.LOOPBACK_TIMEOUT) - self.addCleanup(smtp.close) - self.assertEqual(smtp.send_message(msg), {}) + self.assertEqual(self.serv.last_mailfrom, 'főo@bar.com') self.assertEqual(self.serv.last_rcpttos, ['Dinsdale']) - self.assertEqual(self.serv.last_message.decode(), expected) + self.assertEqual(last_message, expected) self.assertIn('BODY=8BITMIME', self.serv.last_mail_options) self.assertIn('SMTPUTF8', self.serv.last_mail_options) self.assertEqual(self.serv.last_rcpt_options, []) diff --git a/Misc/NEWS.d/next/Library/2025-07-20-09-51-25.bpo-28879.SLDgcy.rst b/Misc/NEWS.d/next/Library/2025-07-20-09-51-25.bpo-28879.SLDgcy.rst new file mode 100644 index 00000000000000..f3fd71b58d89e1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-20-09-51-25.bpo-28879.SLDgcy.rst @@ -0,0 +1,2 @@ +Fix ``smtplib.send_message`` to add a ``Date`` header if it is missing as +per :rfc:`5322`.