After a bunch of fiddling, I finally hit upon the magical sequence to attach a text file to an email (many thanks to previous posts on this service).
I'm left wondering what the lines:
attachment.add_header('Content-Disposition'. . .)
--and--
e_msg = MIMEMultipart('alternative')
actually do.
Can someone unsilence the Mimes for me please (sorry couldn't resist)
import smtplib
from email import Encoders
from email.message import Message
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
smtp_server = "1.2.3.4"
smtp_login = "account"
smpt_password = "password"
server = smtplib.SMTP(smtp_server)
server.login(smtp_login,smtp_password)
f = file("filename.csv")
attachment = MIMEText(f.read())
attachment.add_header('Content-Disposition', 'attachment', filename="filename.csv")
e_msg = MIMEMultipart('alternative')
e_msg.attach(attachment)
e_msg['Subject'] = 'Domestic Toll Monitor'
e_msg['From'] = smtp_account
body = 'Some nifty text goes here'
content = MIMEText(body)
e_msg.attach(content)
server.sendmail(smtp_from, smtp_to, e_msg.as_string())
Basically, MIME is the specification defining email structure. The Multipart structure is designed to allow for multiple types of messages and attachments to be sent within the same message. For example, an email might have a plain text version for backwards compatibility and a rich text or html formatted message for modern clients. Attachments count as a "part", and thus require their own header. In this case, you're adding a "Content-Disposition" type header for the attachment. If you're really interested in what that means, you can read the specification here. As for the "Alternative portion, you're setting the message to multipart and defining the types of parts that you have attached and how the client needs to handle them. There are some standard presets defining various scenarios, but Alternative is something of a wildcard, used when there is a part whose type might not be recognized or handled by most clients. For the record, I believe you also could have used a "Mixed" type. The nice thing about MIME is that while it is complicated, its thoroughly defined and its very easy to look up the specification.
Related
I'm trying to work with email messages in Python 3.7 and struggling with what looks like compatibility issues. The docs mention email.message.Message having an iter_parts method that should allow me to do a non-recursive walk of message parts.
This doesn't exist on messages returned from mailbox messages and it's taken me a while to get it behaving. For example, I can generate a dummy message with:
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'msg 1'
msg.add_alternative("Plain text body", subtype='plain')
msg.add_alternative("<html><body><p>HTML body</p></body></html>", subtype='html')
msg.add_attachment(b"Nothing to see here!", maintype='data', subtype='raw')
and then dump out the parts with:
def iter_parts(msg):
ret = msg.get_content_type()
if msg.is_multipart():
parts = ', '.join(iter_parts(m) for m in msg.iter_parts())
ret = f'{ret} [{parts}]'
return ret
iter_parts(msg)
which gives me: multipart/mixed [multipart/alternative [text/plain, text/plain], data/raw]
but if I save this to a mbox file and reload it:
import mailbox
mbox = mailbox.mbox('/tmp/test.eml')
mbox.add(msg)
iter_parts(mbox[0])
it tells me AttributeError: 'mboxMessage' object has no attribute 'iter_parts'
Initially I thought it might be related to https://stackoverflow.com/a/45804980/1358308 but setting factory=None doesn't seem to do much in Python 3.7.
Am posting my solution, but would like to know if there are better options!
After much poking and reading of source I found that I can instead do:
from email import policy
from email.parser import BytesParser
mbox = mailbox.mbox('/tmp/test.eml', factory=BytesParser(policy=policy.default).parse)
and then I get objects with an iter_parts method.
I have two messages.One is the HTML Message and the other one is a simple plain text message.I am attaching both of them to the MIMEMultipart variable(tmessage) but when the email get send, i can only see the second attached message in my inbox. I cannot figure out why...Here is my code
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
host='smtp.gmail.com'
port=587
message="<h1>Hey i have received a 3rd email message using Python</h1>"
userName='teaching807#gmail.com'
password='teaching807299'
connection = smtplib.SMTP(host,port)
connection.ehlo()
connection.starttls()
_from=userName
_to=userName
connection.login(userName,password)
tmessage = MIMEMultipart("alternative")
tmessage['Subject']="Html Message"
tmessage['From']=_from
tmessage['To']=_to
plain_message = "This is a plain message"
html_message="""<html><body><h1>Students Marks</h1><p>These are the students
Marks</p></body></html>"""
msg1=MIMEText(html_message,'html')
msg2=MIMEText(plain_message,'plain')
tmessage.attach(msg1)
tmessage.attach(msg2)
connection.sendmail(_from,_to,tmessage.as_string())
connection.quit()
In the inbox only msg2 can be seen
By adding two parts, you're offering alternatives. From the python docs:
According to RFC 2046, the last part of a multipart message, in this case the HTML message, is best and preferred.
You're adding the plain text last, making that the prefered one. You'll never see both the plain and html.
According to the bottom part of this documentation, the type "alternative" is simply that, to provide an alternative plain text when HTML is not supported for whatever reason.
You're only viewing one message because your email account/browser supports html.
You will only ever see one email, because only one is sent. What you view depends upon what type of data your email/browser can support.
try removing "alternative" from tmessage, it works for me in Outlook.
tmessage = MIMEMultipart()
I'm working on a script that creates both an HTML and Plaintext email. This didn't seem like a hassle, but I can't hardcode the e-mail into the script I'm using. Allow me to explain why.
I'm using the Zabbix Monitoring system to keep an eye out for my systems. Allow me to use a printer for example. Once one of the toners is close to dying, I want both an HTML and plain text mail to be sent. Zabbix uses several macros however, which can be used to identify what system is being affected by what.
So, the HTML body needs to received before the script is run. Once the HTML body has been received with the correct values, it needs to have a plain copy too. As much as I'd like to have the plain-text contain the correct parameters (copied from the macros) too, I don't consider this a necessity.
This is what my script currently looks like.
#!/usr/bin/env python
list1=[0, 1, 2, 3, 4];
import mimetypes, os, smtplib, sys
from email import encoders
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
me = "x"
you = "y"
DESTINATION=sys.argv[1]
SUBJECT=sys.argv[2]
MESSAGE=sys.argv[3]
PASS='password'
# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Link"
msg['From'] = me
msg['To'] = you
text = "A problem has been detected on {HOST.NAME} \n \n Trigger: {TRIGGER.NAME} \n Status: {TRIGGER.STATUS} \n Severity: {TRIGGER.SEVERITY} "
#html =
# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
#part2 = MIMEText(html, 'html')
# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
#msg.attach(part2)
# Send the message via local SMTP server.
s = smtplib.SMTP('mysmtpserver')
s.ehlo()
s.starttls()
s.ehlo()
s.login(me, PASS)
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(me, you, msg.as_string())
s.quit()
The reason why I can't have them being sent seperately or sending the HTML copy alone is because my e-mails get nuked by SpamAssassin. I'm intending to send certain error messages to my customers. So they are immediately notified when their toner is nearly empty, so they can place a new order. The HTML-email has been set up and has been working without failure.
So to put it onto perspective one more time.
Zabbix recieves an error.
There is a default HTML message written, macros become filled, this then needs to be imported to the script.
The script is then run.
There's an e-mail sent with both HTML content as well as the
plain-text for users that can't read the HTML-emails.
The reason the script is a mess is because I've already tried several options, but I can't seem to figure this one out.
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.
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.