How to send emails with names in headers from Python - python

I need to send nice-looking emails from Python with address headers that contain names - somehow something that never pops up in tutorials.
I'm using email.mime.text.MIMEText() to create the email, but setting msg['To'] = 'á <x#y.cz>' rather than utf8-encoding only the name part, will utf8-encode the whole header value, which of course fails miserably. How to do this correctly?
I have found a sort-of solution Python email module: form header "From" with some unicode name + email but it feels hard to accept such a hack, since there does seem to be some support for handling this automatically in Python's email package in email.headerregistry which should be used automatically as far as I can see, but it doesn't happen.

You have to use the right policy from email.policy to get the correct behaviour.
Wrong Policy
email.message.Message will use email.policy.Compat32 by default. That one was designed for backward-compatibility wih older Python versions and does the wrong thing:
>>> msg = email.message.Message(policy=email.policy.Compat32())
>>> msg['To'] = 'ššššš <ssss#example.com>'
>>> msg.as_bytes()
b'To: =?utf-8?b?xaHFocWhxaHFoSA8c3Nzc0BleGFtcGxlLmNvbT4=?=\n\n'
Correct Policy
email.policy.EmailPolicy will do what you want:
>>> msg = email.message.Message(policy=email.policy.EmailPolicy())
>>> msg['To'] = 'ššššš <ssss#example.com>'
>>> msg.as_bytes()
b'To: =?utf-8?b?xaHFocWhxaHFoQ==?= <ssss#example.com>\n\n'
Python 2.7
With older Python versions (eg 2.7), you have to use the "hack" as you called it:
>>> msg = email.message.Message()
>>> msg['To'] = email.header.Header(u'ššššš').encode() + ' <ssss#example.com>'
>>> msg.as_string()
'To: =?utf-8?b?xaHFocWhxaHFoQ==?= <ssss#example.com>\n\n'

Related

Python smtplib - not sending with variables

I am trying to send an email using the smtplib library. It works fine, but if my message includes a fixed string + variable, it doesn't work.
Here is my code:
destinationEmail = 'destination#gmail.com'
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login("me#gmail.com", "password")
message = f"I am sending this variable: {variableName}"
server.sendmail("me#gmail.com", destinationEmail, message)
Using the above code does not work. I've tried using this format for the message as well:
message = "I am sending this variable: " + variableName
It also does not work. If my message has no variables, it sends successfully, like:
message = "testing"
Another thing I've tried is sending the variable alone, like:
message = variableName
And that works! So I'm really confused what is wrong here. It seems like it doesn't wanna send messages that has a fixed string + variable. BTW the variable I am using is of type string. Even when I use str( ) around the variable, it doesn't work.
This is usually a version problem, but you can always substitute your f-string for message = "I am sending this variable: {}".format(variable_name) which worked for me.
The answer is here: How to send variable value via email with smtplib in python3?
Use MIMEText to construct the message (with 'subject' 'from' and 'to'). #tax evader was right about the subject thing. I guess it requires a subject.

How to get sender mailbox from email.message.Message object?

First I connect to my own mailbox using such code:
import email
import imaplib
imap_server = imaplib.IMAP4_SSL('imap.gmail.com', 993)
imap_server.login("myemail", "mypassword")
imap_server.select('inbox')
Then I get the last email on it in the way like:
_, data = imap_server.search(None, 'ALL')
id_list = data[0].split()
_, data = imap_server.fetch(id_list[-1], '(RFC822)')
rawmail = data[0][1]
prev_mail = email.message_from_bytes(rawmail)
And after that, I need to get sender email from prev_mail variable.
I tried it myself a little bit, and figured out that prev_mail['From'] is usually sth like this: "John Snow" <johnsnow#gmail.com>, so that means I can just grab the <*> part and use it.
But the question is: will it always be like this so I could use my method? And are there any better ways to do this rather than mine?
EDIT:
There are multiple variants of From: headers. Python 3.8 has a good parser for this in the email.header module. But perhaps the Return-Path: header would actually be closer to what you really want? - tripleee
Thank you very much, tripleee, that's exactly what I needed!

Python 3 Reciving email problems

I'm writing a script to receive emails from my gmail email in python. I'm managing to download the raw email however I am then unable to access certain types of it, E.G BODY, TO, FROM etc.
import imaplib, email
msrvr = imaplib.IMAP4_SSL('imap.gmail.com', 993)
unm = 'stackoverflow#gmail.com'
pwd = 'lovetocode'
msrvr.login(unm,pwd)
stat,cnt = msrvr.select('Inbox')
stat, dta = msrvr.fetch(cnt[0], '(RFC822)')
b = email.message_from_string(str(dta))
print(b)
print(b['[To]'])
msrvr.close()
msrvr.logout()
Where am I going wrong?
You might find it easier to use native Python Google SDK's for working with their email:
https://developers.google.com/appengine/docs/python/mail/
The imaplib module you are using is will only give you a subset of all gmail features..
Here's some code that parses an email and prints some header fields:
msg = email.message_from_string(raw_email)
for field in ('From', 'Subject', 'Received', 'Message-ID'):
print '{0}: {1}'.format(field, msg[field])
For debugging, also print the raw parts of the Message object:
print msg.__dict__
(Note: I'm using Python2.7, but I believe there's not much difference.)

Python email module: form header "From" with some unicode name + email

I'm generating email with the help of Python email module.
Here are few lines of code, which demonstrates my question:
msg = email.MIMEMultipart.MIMEMultipart('alternative')
msg['From'] = "somemail#somedomain.com"
msg.as_string()
Out[7]: 'Content-Type: multipart/alternative;\n boundary="===============9006870443159801881=="\nMIME-Version: 1.0\nFrom: somemail#somedomain.com\n\n--===============9006870443159801881==\n\n--===============9006870443159801881==--'
As you can see, everything is okay here, From field contains email ant it is cool. But what if I want to add some name before email? Especially unicode one:
In [8]: u.get_full_name()
Out[8]: u'\u0414\u0438\u043c\u0430 \u0426\u0443\u043a\u0430\u043d\u043e\u0432'
In [9]: msg = email.MIMEMultipart.MIMEMultipart('alternative')
In [10]: msg['From'] = "%s <%s>" % (u.get_full_name(), "email#at.com")
In [11]: msg.as_string()
Out[11]: 'Content-Type: multipart/alternative;\n boundary="===============5792069034892928634=="\nMIME-Version: 1.0\nFrom: =?utf-8?b?0JTQuNC80LAg0KbRg9C60LDQvdC+0LIgPGVtYWlsQGF0LmNvbT4=?=\n\n--===============5792069034892928634==\n\n--===============5792069034892928634==--'
Here you can see, that all the string (name, email) was encoded in base64 (and it is even quite logical, how MIMEMultipart will know that string contains unicode and non-unicode parts).
So, my question is: how do I have to tell email module to make me pretty "From" header like:
From: =?UTF-8?B?0JLQmtC+0L3RgtCw0LrRgtC1?= <admin#notify.vk.com> ?
Also, I've learned a little RFC2822 (http://www.faqs.org/rfcs/rfc2822.html , p.3.6.2). It tells:
The originator fields indicate the mailbox(es) of the source of the
message. The "From:" field specifies the author(s) of the message,
that is, the mailbox(es) of the person(s) or system(s) responsible
for the writing of the message. The "Sender:" field specifies the
mailbox of the agent responsible for the actual transmission of the
message. For example, if a secretary were to send a message for
another person, the mailbox of the secretary would appear in the
"Sender:" field and the mailbox of the actual author would appear in
the "From:" field. If the originator of the message can be indicated
by a single mailbox and the author and transmitter are identical, the
"Sender:" field SHOULD NOT be used. Otherwise, both fields SHOULD
appear.
Does it mean that I should combine these two headers? (From and Sender). I'm a bit confused, because I noticed a lot of emails in my gmail (looking through "Show original") where in From field name and email are presented.
Thanks for help.
You need to encode the name part separately using email.header.Header:
from email.MIMEMultipart import MIMEMultipart
from email.header import Header
from email.utils import formataddr
author = formataddr((str(Header(u'Alał', 'utf-8')), "somemail#somedomain.com"))
msg = MIMEMultipart('alternative')
msg['From'] = author
print msg
I hope this will help.

Python 3 smtplib send with unicode characters

I'm having a problem emailing unicode characters using smtplib in Python 3. This fails in 3.1.1, but works in 2.5.4:
import smtplib
from email.mime.text import MIMEText
sender = to = 'ABC#DEF.com'
server = 'smtp.DEF.com'
msg = MIMEText('€10')
msg['Subject'] = 'Hello'
msg['From'] = sender
msg['To'] = to
s = smtplib.SMTP(server)
s.sendmail(sender, [to], msg.as_string())
s.quit()
I tried an example from the docs, which also failed. http://docs.python.org/3.1/library/email-examples.html, the Send the contents of a directory as a MIME message example
Any suggestions?
The key is in the docs:
class email.mime.text.MIMEText(_text, _subtype='plain', _charset='us-ascii')
A subclass of MIMENonMultipart, the
MIMEText class is used to create MIME
objects of major type text. _text is
the string for the payload. _subtype
is the minor type and defaults to
plain. _charset is the character set
of the text and is passed as a
parameter to the MIMENonMultipart
constructor; it defaults to us-ascii.
No guessing or encoding is performed
on the text data.
So what you need is clearly, not msg = MIMEText('€10'), but rather:
msg = MIMEText('€10'.encode('utf-8'), _charset='utf-8')
While not all that clearly documented, sendmail needs a byte-string, not a Unicode one (that's what the SMTP protocol specifies); look to what msg.as_string() looks like for each of the two ways of building it -- given the "no guessing or encoding", your way still has that euro character in there (and no way for sendmail to turn it into a bytestring), mine doesn't (and utf-8 is clearly specified throughout).
_charset parameter of MIMEText defaults to us-ascii according to the docs. Since € is not from us-ascii set it isn't working.
example in the docs that you've tried clearly states:
For this example, assume that the text file contains only ASCII characters.
You could use .get_charset method on your message to investigate the charset, there is incidentally .set_charset as well.

Categories