0

I'm attempting to send a sample email, but get the following error:

>>> import smtplib
>>> from email.mime.text import MIMEText
>>> def send_email(subj, msg, from_addr, *to, host="localhost", port=1025, **headers):
...   email = MIMEText(msg)
...   email['Subject'] = subj
...   email['From'] = from_addr
...   for h, v in headers.items():
...     print("Headers - {} Value {} ".format(h, v))
...     email[h] = v
...   sender = smtplib.SMTP(host,port)
...   for addr in to:
...     del email['To']
...     email['To'] = addr
...     sender.sendmail(from_addr, addr, email.as_string())
...   sender.quit()
...
>>> headers={'Reply-To': 'me2@example.com'}
>>> send_email("first email", "test", "first@example.com", ("p1@example.com", "p2@example.com"), headers=headers)
Headers - headers Value {'Reply-To': 'me2@example.com'} 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 12, in send_email
  File "/usr/lib/python3.5/email/message.py", line 159, in as_string
    g.flatten(self, unixfrom=unixfrom)
  File "/usr/lib/python3.5/email/generator.py", line 115, in flatten
    self._write(msg)
  File "/usr/lib/python3.5/email/generator.py", line 195, in _write
    self._write_headers(msg)
  File "/usr/lib/python3.5/email/generator.py", line 222, in _write_headers
    self.write(self.policy.fold(h, v))
  File "/usr/lib/python3.5/email/_policybase.py", line 322, in fold
    return self._fold(name, value, sanitize=True)
  File "/usr/lib/python3.5/email/_policybase.py", line 360, in _fold
    parts.append(h.encode(linesep=self.linesep,
AttributeError: 'dict' object has no attribute 'encode'

When I omit the optional header dictionary, then the email is successfully sent. The **param requires a dictionary, is that correct? Can anybody suggest a remedy for the error?

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
alortimor
  • 351
  • 2
  • 7
  • 18

1 Answers1

1

You are misunderstanding how *args and **kwargs work. They capture additional positional and keyword arguments, while you are passing in an extra tuple and an extra dictionary as (...) and headers=headers, respectively.

That means that to is now set to (("p1@example.com", "p2@example.com"),) (a tuple containing a single tuple), and headers is set to {'headers': {'Reply-To': 'me2@example.com'}} (a dictionary containing another dictionary).

You see the latter in your output:

Headers - headers Value {'Reply-To': 'me2@example.com'} 

That's the headers key, referencing a dictionary.

Pass in the to values as separate arguments, and use the **kwargs call syntax to pass in the headers:

headers={'Reply-To': 'me2@example.com'}
send_email("first email", "test", 
           "first@example.com", "p1@example.com", "p2@example.com", 
           **headers)

**headers applies each key-value pair in that dictionary as a separate keyword argument.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • The email output for `send_email("A model subject", "The message contents", "from@example.com", "to1@example.com", "to2@example.com", "Reply-To", "me2@example.com")` is `---------- MESSAGE FOLLOWS ---------- Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: A model subject From: from@example.com To: me2@example.com X-Peer: 127.0.0.1` which is not what I want, since the Reply-To: me2@example.com is not appearing Also, the to list is incorrect. – alortimor Sep 04 '16 at 11:20
  • @alortimor: That's not the expression I used in my answer. You are sending the email to (among others), `"Reply-To"` and `"me2@example.com")` because those are positional parameters. Since you can't use `Reply-To` as a direct keyword argument (that header is not a valid Python identifier), only using `**{'Reply-To': 'me2@example.com'}` or a separate dictionary and `**headers` as I used in my answer will pass in that key-value pair into the `headers` dictionary in the function. – Martijn Pieters Sep 04 '16 at 15:31