I'm using Python email.mime lib to write emails, and I created two MIMEText objects and then attached them to Message as text (not as attachment), and as a result I got the MIME document as follows, as you can see there are two text objects, one is of type plain and the other is of type html, my question is that I can only see the latter text object (here is the html) in some mail clients, while I can see both text objects in some other mail clients (for example, live.com), so what caused this?
Content-Type: multipart/mixed; boundary="===============0542266593=="
MIME-Version: 1.0
FROM: john.smith#NYU.com
TO: john.smith#live.com, john.smith#gmail.com
SUBJECT: =?utf-8?q?A_Greeting_From_Postman?=
--===============0542266593==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
SGkhCkhvdyBhcmUgeW91PwpIZXJlIGlzIHRoZSBsaW5rIHlvdSB3YW50ZWQ6Cmh0dHA6Ly93d3cu
cHl0aG9uLm9yZw==
--===============0542266593==
Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
ICAgICAgICA8aHRtbD4KICAgICAgICAgIDxoZWFkPjwvaGVhZD4KICAgICAgICAgIDxib2R5Pgog
ICAgICAgICAgICA8cD5IaSE8YnI+CiAgICAgICAgICAgICAgIEhvdyBhcmUgeW91Pzxicj4KICAg
ICAgICAgICAgICAgSGVyZSBpcyB0aGUgPGEgaHJlZj0iaHR0cDovL3d3dy5weXRob24ub3JnIj5s
aW5rPC9hPiB5b3Ugd2FudGVkLgogICAgICAgICAgICA8L3A+CiAgICAgICAgICA8L2JvZHk+CiAg
ICAgICAgPC9odG1sPgogICAgICAgIA==
--===============0542266593==--
You have specified 'multipart/mixed' as the mime type. If you want only one item to be displayed, specify 'multipart/alternative', as so:
email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.encoders import encode_base64
# Note: 'alternative' means only display one of the items.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Hello"
msg['From'] = 'me#example.com'
msg['To'] = 'you#example.com'
msg.attach(MIMEText('Hello!', 'plain'))
msg.attach(MIMEText('<b>Hello!</b>', 'html'))
# Not required, but you had it in your example, so I kept it.
for i in msg.get_payload():
encode_base64(i)
print msg.as_string()
Related
I use the following code to send an e-mail with a pdf attachment. For most receivers this works without any issues but some clients show the pdf as corrupt or not at all. Thus I think there is probably something wrong and most clients are just forgiving enough to make it work anyway. Unfortunately, at this point I am out of ideas as I tried so many header combinations - all without success.
The pdf is base64 encoded.
def sendMail(receiver, pdf):
marker = "AUNIQUEMARKER"
message = """Subject: The Subject
From: {sender}
To: {receiver}
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary={marker}
--{marker}
Content-Type: text/plain; charset="utf-8"
Text goes here.
--{marker}
Content-Type: application/pdf; name="{filename}"
Content-Transfer-Encoding:base64
Content-Disposition: attachment; filename={filename}
{pdfcontent}
--{marker}--
""".format(marker=marker, sender="some#sender.com", receiver=receiver, filename="Test.pdf", pdfcontent=pdf)
port = 587
smtp_server = "some.server.com"
context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
server.starttls(context=context)
server.login("user", "password")
server.sendmail("some#sender.com", [receiver, "cc#sender.com"], message.encode())
In case it is relevant, the pdf is created via LaTex as follows
pdfl = PDFLaTeX.from_texfile('latex/test.tex')
pdf, log, completed_process = pdfl.create_pdf(keep_pdf_file=False, keep_log_file=False)
pdfBase64 = base64.b64encode(pdf).decode()
Thanks for any help.
PS: Not showing the attachment at all might be fixed as I switched from Content-Type: multipart/alternative to multipart/mixed.
Well, apparently the base64 block should contain a newline every 76 characters. In my case that means I had to switch from base64.b64encode to base64.encodebytes as the latter does exactly this.
I am getting an "error: ValueError: Cannot convert mixed to alternative."
I get this error when I insert the open image and msg.add_attachment block (highlighted in btw #### ####). Without it, the code runs fine. I need to send the email as html and with the image attachment.
import os
import imghdr
from email.message import EmailMessage
import smtplib
EMAIL_ADDRESS = os.environ.get('EMAIL-USER')
EMAIL_PASSWORD = os.environ.get('EMAIL-PASS')
Message0 = "HelloWorld1"
Message1 = "HelloWorld2"
msg = EmailMessage()
msg['Subject'] = 'Hello WORLD'
msg['From'] = EMAIL_ADDRESS
msg['To'] = EMAIL_ADDRESS
msg.set_content('This is a plain text email, see HTML format')
########################################################################
with open('screenshot.png', 'rb') as f:
file_data = f.read()
file_type = imghdr.what(f.name)
file_name = f.name
msg.add_attachment(file_data, maintype='image', subtype=file_type, filename=file_name)
#########################################################################
msg.add_alternative("""\
<!DOCTYPE html>
<html>
<body>
<h1 style="color:Blue;">Hello World</h1>
{Message0}
{Message1}
</body>
</html>
""".format(**locals()), subtype='html')
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
smtp.send_message(msg)
print("email sent")
For the end result, I need to be able to send an email via Python and attach images.
An e-mail can consist of a single part, or it can be a multi-part message.
If it is a multi-part message, it will usually be either a multipart/alternative, or a multipart/mixed.
multipart/alternative means there are 2 or more versions of the same content (e.g. plain text and html)
multipart/mixed is used when multiple different contents need to be packed together (e.g. an email and an attachment)
What actually happens when multipart is used is that email consists of a "multipart" container which contains additional parts, e.g. for text+html it is something like this:
multipart/alternative part
text/plain part
text/html part
In case of an email with attachment, you can have something like this:
multipart/mixed part
text/plain part
image/png part
So, the container is either mixed or alternative, but cannot be both. So, how to have both? You can nest them, e.g.:
multipart/mixed part
multipart/alternative part
text/plain part
text/html part
image/png part
So, now you have an email which consists of a message and an attachment, and the message has both plain text and html.
Now, in code, this is the basic idea:
msg = EmailMessage()
msg['Subject'] = 'Subject'
msg['From'] = 'from#email'
msg['To'] = 'to#email'
msg.set_content('This is a plain text')
msg.add_attachment(b'xxxxxx', maintype='image', subtype='png', filename='image.png')
# Now there are plain text and attachment.
# HTML should be added as alternative to the plain text part:
text_part, attachment_part = msg.iter_parts()
text_part.add_alternative("<p>html contents</p>", subtype='html')
BTW, you can then see what is in each part this way:
>>> plain_text_part, html_text_part = text_part.iter_parts()
>>> print(plain_text_part)
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
This is a plain text
>>> print(html_text_part)
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
<p>html contents</p>
>>> print(attachment_part)
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="image.png"
MIME-Version: 1.0
eHh4eHh4
>>> print(msg)
Subject: Subject
From: from#email
To: to#email
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============2219697219721248811=="
--===============2219697219721248811==
Content-Type: multipart/alternative;
boundary="===============5680305804901241482=="
--===============5680305804901241482==
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit
This is a plain text
--===============5680305804901241482==
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
<p>html contents</p>
--===============5680305804901241482==--
--===============2219697219721248811==
Content-Type: image/png
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="image.png"
MIME-Version: 1.0
eHh4eHh4
--===============2219697219721248811==--
This is a complementary example to the one provided by #zvone. The difference here is that I want to have a mail with 2 versions. One in plain-text with an image as an attachement. The other part being the html with embedded image.
So the layout of the mail is like so:
multipart/mixed part
multipart/alternative part
multipart/related part (html with img)
text/html part
image/png part
text/plain part (plain text)
image/png part (attachement)
plain-text version: textfile.txt
Hello World
html version: mail_content.html
<html>
<head></head>
<body>
<h1>Hello World</h1>
<img src="cid:{graph_example_img_cid}" />
</body>
</html>
And of course the Python code (tested for Python 3.10). The embedding of the image follows the example from latest Python Standard Library of email.
Python code: main.py
# var
mail_server: str = "****smtp****"
user: str = "****#****.com"
me = user
you = user
# imports
import smtplib # mail actual sending function
import imghdr # And imghdr to find the types of our images
from email.message import EmailMessage
from email.utils import make_msgid
import datetime
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
textfile_filepath: str = "textfile.txt"
with open(textfile_filepath) as fp:
# Create a text/plain message
msg = EmailMessage()
msg.set_content(fp.read())
# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'Test Email with 2 versions'
msg['From'] = me
msg['To'] = you
# attachements
# Open the files in binary mode. Use imghdr to figure out the
# MIME subtype for each specific image.
image_filepath: str = "test_image.png"
with open(image_filepath, 'rb') as fp:
img_data = fp.read()
msg.add_attachment(img_data, maintype='image', subtype=imghdr.what(None, img_data))
text_part, attachment_part = msg.iter_parts()
## HTML alternative using Content ID
html_content_filepath: str = "mail_content.html"
graph_cid = make_msgid()
with open(html_content_filepath) as fp:
# Create a text/plain message
#msg = MIMEText(fp.read())
raw_html: str = fp.read()
# note that we needed to peel the <> off the msgid for use in the html.
html_content: str = raw_html.format(graph_example_img_cid=graph_cid[1:-1])
# add to email message
text_part.add_alternative(html_content,subtype='html')
# Now add the related image to the html part.
with open(image_filepath, 'rb') as img:
text_part, html_part = text_part.iter_parts()
html_part.add_related(img.read(), 'image', 'jpeg', cid=graph_cid)
# Send the message via our own SMTP server, but don't include the envelope header.
s = smtplib.SMTP(mail_server)
s.sendmail(me, [you], msg.as_string())
s.quit()
# log
current_time = datetime.datetime.now()
print("{} >>> Email sent (From: {}, To: {})".format(
current_time.isoformat(), me, you
))
I'm having trouble with a bit of code sending an email with attachments AND a Subject line.
# Code exerpt from Oli: http://stackoverflow.com/questions/3362600/how-to-send-email-attachments-with-python
# Emails aren't sending with a subject--need to fix this.
def send_mail(self, send_from, send_to, subject, text, files=None, server="localhost"):
assert isinstance(send_to, list)
msg = MIMEMultipart(
Subject=subject,
From=send_from,
To=COMMASPACE.join(send_to),
Date=formatdate(localtime=True)
)
msg.attach(MIMEText(text))
for f in files or []:
with open(f, "rb") as fil:
msg.attach(MIMEApplication(
fil.read(),
Content_Disposition='attachment; filename="%s"' % basename(f),
Name=basename(f)
))
smtp = smtplib.SMTP(server)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
This code sends an email fine, but it is not deliminating the 'Subject' line and the emails it sends have a subject line of "NO SUBJECT.' Here's what it shows when I print the first part of the MIME msg:
From nobody Thu Oct 29 16:17:38 2015
Content-Type: multipart/mixed; date="Thu, 29 Oct 2015 16:17:38 +0000";
to="me#email.com";
from="someserver#somewhere.com"; subject="TESTING";
boundary="===============0622475305469306134=="
MIME-Version: 1.0
--===============0622475305469306134==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Here we go, oh! ho! ho!
--===============0622475305469306134==
Content-Type: application/octet-stream; Content- Disposition="attachment;
filename=\"Log_Mill.py\""; Name="Log_Mill.py"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
I might be able to figure it out if I plug away for hours and hours, but I'm hoping to avoid the extra work for such a trivial fix.
Any help is appreciated!
You are assigning the Subject etc. as attributes of the multipart container, but that's incorrect. The headers you want to specify should be passed to the msg itself as headers instead, like this:
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
The output should look more like
From nobody Thu Oct 29 16:17:38 2015
Date: Thu, 29 Oct 2015 16:17:38 +0000
To: <me#email.com>
From: <someserver#somewhere.com>
Subject: TESTING
Content-Type: multipart/mixed;
boundary="===============0622475305469306134=="
MIME-Version: 1.0
--===============0622475305469306134==
Content-Type: text/plain; .......
You could also use a package specialised for writing HTML emails, showing pictures inline and easily attach files!
The package I'm referring to is yagmail and I'm the developer/maintainer.
import yagmail
yag = yagmail.SMTP('email#email.com', 'email_pwd')
file_names = ['/local/path/f.mp3', '/local/path/f.txt', '/local/path/f.avi']
yag.send('to#email.com', 'Sample subject', contents = ['This is text'] + filenames)
That's all there is to it.
Use pip install yagmail to obtain your copy.
Contents can be a list where you also add text, you can just only have the file_names as contents, awesome no?
It reads the file, magically determines the encoding, and attached it :)
Read the github for other tricks like passwordless scripts, aliasing and what not.
Python supports a quite functional MIME-Library called email.mime.
What I want to achieve is to get a MIME Part containing plain UTF-8 text to be encoded as quoted printables and not as base64. Although all functionallity is available in the library, I did not manage to use it:
Example:
import email.mime.text, email.encoders
m=email.mime.text.MIMEText(u'This is the text containing ünicöde', _charset='utf-8')
m.as_string()
# => Leads to a base64-encoded message, as base64 is the default.
email.encoders.encode_quopri(m)
m.as_string()
# => Leads to a strange message
The last command leads to a strange message:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Transfer-Encoding: quoted-printable
GhpcyBpcyB0aGUgdGV4dCBjb250YWluaW5nIMO8bmljw7ZkZQ=3D=3D
This is obviously not encoded as quoted printables, the double transfer-encoding header is strange at last (if not illegal).
How can I get my text encoded as quoted printables in the mime-message?
Okay, I got one solution which is very hacky, but at least it leads into some direction: MIMEText assumes base64 and I don't know how to change this. For this reason I use MIMENonMultipart:
import email.mime, email.mime.nonmultipart, email.charset
m=email.mime.nonmultipart.MIMENonMultipart('text', 'plain', charset='utf-8')
#Construct a new charset which uses Quoted Printables (base64 is default)
cs=email.charset.Charset('utf-8')
cs.body_encoding = email.charset.QP
#Now set the content using the new charset
m.set_payload(u'This is the text containing ünicöde', charset=cs)
Now the message seems to be encoded correctly:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
This is the text containing =C3=BCnic=C3=B6de
One can even construct a new class which hides the complexity:
class MIMEUTF8QPText(email.mime.nonmultipart.MIMENonMultipart):
def __init__(self, payload):
email.mime.nonmultipart.MIMENonMultipart.__init__(self, 'text', 'plain',
charset='utf-8')
utf8qp=email.charset.Charset('utf-8')
utf8qp.body_encoding=email.charset.QP
self.set_payload(payload, charset=utf8qp)
And use it like this:
m = MIMEUTF8QPText(u'This is the text containing ünicöde')
m.as_string()
In Python 3 you do not need your hack:
import email
# Construct a new charset which uses Quoted Printables (base64 is default)
cs = email.charset.Charset('utf-8')
cs.body_encoding = email.charset.QP
m = email.mime.text.MIMEText(u'This is the text containing ünicöde', 'plain', _charset=cs)
print(m.as_string())
Adapted from issue 1525919 and tested on python 2.7:
from email.Message import Message
from email.Charset import Charset, QP
text = "\xc3\xa1 = \xc3\xa9"
msg = Message()
charset = Charset('utf-8')
charset.header_encoding = QP
charset.body_encoding = QP
msg.set_charset(charset)
msg.set_payload(msg._charset.body_encode(text))
print msg.as_string()
will give you:
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
=C3=A1 =3D =C3=A9
Also see this response from a Python committer.
I saved the whole message as xx.eml, but some mails body tells that mail is encoding by base64 at the first line, for example:
charset="utf-8" Content-Transfer-Encoding: base64
charset="gb2312" Content-Transfer-Encoding: base64
I tried to get the keys of body[0][1], but there is no content-transfer-encoding field (only content-type).
How can I process that mails?
def saveMail(conn, num):
typ, body = conn.fetch(num, 'RFC822')
message = open(emldirPath + '\\' + num + '.eml', 'w+')
message.write(str(email.message_from_string(body[0][1])))
print email.message_from_string(body[0][1]).keys()
#['Received', 'Return-Path', 'Received', 'Received', 'Date', 'From', 'To',
# 'Subject', 'Message-ID', 'X-mailer', 'Mime-Version', 'X-MIMETrack',
# 'Content-Type', 'X-Coremail-Antispam']
message.close()
I found the problem, it's not decoding problem.
right mail as follow:
------=_Part_446950_1309705579.1326378953207
Content-Type: text/plain; charset=GBK
Content-Transfer-Encoding: base64
what my program download:
------=_Part_446950_1309705579.1326378953207
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: base64
when my program save the .eml file, it change line after 'text/plain;'
therefore outlook express can't parse the mail
if I edit the line to ""Content-Type: text/html;charset="utf-8"",
it works
Now the question is: how to edit my program to not let it change line?
Emails that are transfered as BASE64 must set Content-Transfer-Encoding. However you are most likely dealing with a MIME/Multipart message (e.g. both text/plain and HTML in the same message), in which case the transfer encoding is set separately for each part. You can test with is_multipart() or if Content-Type is multipart/alternative. If that is the case you use walk to iterate over the different parts.
EDIT: It is quite normal to send text/plain using quoted-printable and HTML using BASE64.
Content-Type: multipart/alternative; boundary="=_d6644db1a848db3cb25f2a8973539487"
Subject: multipart sample
From: Foo Bar <foo#example.net>
To: Fred Flintstone <fred#example.net>
--=_d6644db1a848db3cb25f2a8973539487
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=utf-8
SOME BASE64 HERE
--=_d6644db1a848db3cb25f2a8973539487
Content-Transfer-Encoding: base64
Content-Type: text/html; charset=utf-8
AND SOME OTHER BASE64 HERE