'ValueError: There may be at most 1 To headers in a message

I am trying to write a very basic email sending script. Here is my code ..

import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg.set_content("Test message.")
msg['Subject'] = "Test Subject!!!"
msg['From'] = "[email protected]"

email_list = ["[email protected]", "[email protected]"]

for email in email_list:
    msg['To'] = email
    server = smtplib.SMTP(host='smtp.gmail.com', port=587)
    server.starttls()
    server.login("[email protected]", "mypassword")
    server.send_message(msg)
    server.quit()

the script should send mail to multiple recipients so, I need to change the msg['To'] field when iterating through loop But I get the following error in traceback bellow.

Traceback (most recent call last):
  File "exp.py", line 66, in <module>
    msg['To'] = email
  File "/usr/lib/python3.8/email/message.py", line 407, in __setitem__
    raise ValueError("There may be at most {} {} headers "
ValueError: There may be at most 1 To headers in a message

How do I solve ? Please help. Thank you..



Solution 1:[1]

Clean the 'To' property of the message.

for email in email_list:
    msg['To'] = email
    server = smtplib.SMTP(host='smtp.gmail.com', port=587)
    server.starttls()
    server.login("[email protected]", "mypassword")
    server.send_message(msg)
    server.quit()
    del msg['To]

Below is the code that throws the exception: (\Python385\Lib\email\message.py)

def __setitem__(self, name, val):
    """Set the value of a header.

    Note: this does not overwrite an existing header with the same field
    name.  Use __delitem__() first to delete any existing headers.
    """
    max_count = self.policy.header_max_count(name)
    if max_count:
        lname = name.lower()
        found = 0
        for k, v in self._headers:
            if k.lower() == lname:
                found += 1
                if found >= max_count:
                    raise ValueError("There may be at most {} {} headers "
                                     "in a message".format(max_count, name))
    self._headers.append(self.policy.header_store_parse(name, val))

Solution 2:[2]

Not knowing the inner working of the EmailMessage class, what I can assume is that every call to __setitem__ writes to the head of the email message, so by calling it in a loop, the header is being written multiple times, what I'd recommend is that you make an email message for every email you'll send, but create only one server:

server = smtplib.SMTP(host='smtp.gmail.com', port=587)
server.starttls()
server.login("[email protected]", "mypassword")
email_list = ["[email protected]", "[email protected]"]
for email in email_list:
    msg = EmailMessage()
    msg.set_content("Test message.")
    msg['Subject'] = "Test Subject!!!"
    msg['From'] = "[email protected]"
    msg['To'] = email
    server.send_message(msg)
server.quit()

Only if you need for the messages to be sent separately. If you want to send the same message to everyone at the same time you could do something like

msg['To'] = ', '.join(email_list)

Solution 3:[3]

If you have a list of addresses, and some of them include a name/title, then I think this is the correct way to do it. Please note that parseaddr + formataddr pair may not be needed, but parseaddr can correct some malformed recipients.

from email.header import Charset
from email.message import EmailMessage, MIMEPart
from email.utils import formataddr, parseaddr

test_recipients = [
        "Mr. John Doe <[email protected]>",
        "Mr. Jane Doe <[email protected]>",
        "[email protected]"
]
to_header= []
for raw_address in (test_recipients):
    # Parse and recreate
    title, email = parseaddr(raw_address)
    if title and email:
        to_header.append(f"{title} <{email}>")
    elif email:
        to_header.append(email)
# Encode after join
message.add_header("To", Charset("utf-8").header_encode(", ".join(to_header)))

Solution 4:[4]

if you just delete server.quit() from loop and add del msg['to'] then there is no error

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 balderman
Solution 2 fixmycode
Solution 3 nagylzs
Solution 4 mohit sinha