My suspicion is that it attaches it the first time from
{{{extra_headers}}} in {{{self._set_list_header_if_not_empty(msg, 'To',
self.to)}}} at
[https://github.com/django/django/blob/8fa7c2ae88aee7a2b54a745d25ed38152aad2591/django/core/mail/message.py#L266
django.core.mail.message:266] and the second time again from
{{{extra_headers}}} at
[https://github.com/django/django/blob/8fa7c2ae88aee7a2b54a745d25ed38152aad2591/django/core/mail/message.py#L282
django.core.mail.message:282]
{{{#!python
message = EmailMessage(
subject="test subject",
body="test body",
from_email="fr...@example.com",
to=["gu...@example.com"],
headers={
"To": ", ".join(["gu...@example.com", "gu...@example.com",
"gu...@example.com"]),
},
)
}}}
For example, here is a Python 3.9.18 shell output for the `EmailMessage`
above that shows the `To` header appears twice.
{{{#!python
>>> from django.core.mail import EmailMessage
>>> message = EmailMessage(subject="test subject", body="test body",
from_email="fr...@example.com",to=["gu...@example.com"], headers={"To": ",
".join(["gu...@example.com", "gu...@example.com", "gu...@example.com"])})
>>> print(list(message.message().raw_items()))
[('Content-Type', 'text/plain; charset="utf-8"'), ('MIME-Version', '1.0'),
('Content-Transfer-Encoding', '7bit'), ('Subject', 'test subject'),
('From', 'fr...@example.com'), ('To', 'gu...@example.com, gu...@example.com,
gu...@example.com'), ('Date', 'Wed, 13 Dec 2023 15:59:31 -0000'),
('Message-ID', '<170248317136.759.5778419642073676754@036d358ca984>'),
('To', 'gu...@example.com, gu...@example.com, gu...@example.com')]
}}}
My current workaround is to override the `EmailMessage::message` method to
skip adding the {{{To}}}, {{{Cc}}}, {{{From}}}, {{{Reply-To}}} headers the
second time, since we're already attaching them the first time above. In
other words:
{{{#!diff
@@ -21,7 +21,7 @@
# Use cached DNS_NAME for performance
msg['Message-ID'] = make_msgid(domain=DNS_NAME)
for name, value in self.extra_headers.items():
- if name.lower() != 'from': # From is already handled
+ if name.lower() not in ('from', 'to', 'cc', 'reply-to'): #
These are already handled
msg[name] = value
return msg
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35033>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/35033#comment:1>
* needs_tests: 0 => 1
* easy: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/35033#comment:2>
* component: Uncategorized => Core (Mail)
Old description:
New description:
I've provided a patch for this here:
[https://github.com/django/django/pull/17606 django/django#17606]
--
--
Ticket URL: <https://code.djangoproject.com/ticket/35033#comment:3>
* cc: Adam Johnson (added)
* needs_better_patch: 0 => 1
* version: 3.2 => dev
* easy: 1 => 0
Comment:
Reproduced, and accepting based on the RFC which states:
{{{
+----------------+--------+------------+----------------------------+
| Field | Min | Max number | Notes |
| | number | | |
+----------------+--------+------------+----------------------------+
| to | 0 | 1 | |
}}}
This is related to #9233, and I would encourage for the solution to this
ticket to cover for all those headers that should provide at most 1
occurrence.
--
Ticket URL: <https://code.djangoproject.com/ticket/35033#comment:4>
* stage: Unreviewed => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/35033#comment:5>