MIME Attachments won't send with Subject Line - python

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.

Related

Multipart E-Mail missing or corrupt attachment

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.

Python - How to send an html email and attach an image with Python?

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
))

Python sendmail, error

I am getting an additional content as given bellow when I am sending mail from unix server using python sendmail.This content is displayed in the mail.
From nobody Mon Dec 18 09:36:01 2017 Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit
My code is as follows.
#reading data from file
data = MIMEText(file('%s'%file_name).read())
#writing the content as html
content = MIMEText("<!DOCTYPE html><html><head><title></title></head><body>"+'%s'%data+"</body></html>", "html")
msg = MIMEMultipart("alternative")
msg["From"] = "sender#mail.com"
msg["To"] = "mymailid#mail.com"
msg["Subject"] = "python mail"
msg.attach(content)
p = Popen(["/usr/sbin/sendmail", "-t","-oi"], stdin=PIPE,universal_newlines=True)
p.communicate(msg.as_string())
You are constructing the email content in two parts, as data and content. You need to explicitly confirm that both are HTML. So change
data = MIMEText(file('%s'%file_name).read())
to
data = MIMEText(file('%s'%file_name).read(), "html")
You should look at the message string. The message you see is not a warning, it is just what you have writen into the message with:
data = MIMEText(file('%s'%file_name).read())
content = MIMEText("<!DOCTYPE html><html><head><title></title></head><body>"
+'%s'%data+"</body></html>", "html")
data.as_string() actually contains Content-Type: text/plain; ... because it has been added by the first MIMEText line, when you want to include it into the body of a HTML page.
What you really want is probably:
data = file(file_name).read()
content = MIMEText("<!DOCTYPE html><html><head><title></title></head><body>"
+'%s'%data+"</body></html>", "html")
But I also think that you do not need to include it into another level with a MIMEMultipart("alternative"): msg = content is probably enough.
Finally, I do not think that explicitely starting a new process to execute sendmail is really overkill, when the smtplib module from the standard library aloready knows how to send a message:
import smtplib
server = smtplib.SMTP()
server.send_message(msg)
server.quit()

smtp send email and why one attachment can have two Content-Type?

I'm trying use smtp to send email with attachment.And when I get raw email,there are two content-type for one attachment.
How can I just get one content-type?And the two type impact each other?
Thanks for any help!
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
server = smtplib.SMTP()
server.connect("smtp.XX.com")
server.login("","")
msg = MIMEMultipart("")
msg['From'] = ""
msg['Subject'] = "titlesub"
part = MIMEApplication(open("D:\data.txt", 'rb').read())
filename="data.txt"
#part['Content-Type']="application/pdf"
part.add_header('Content-Type','application/pdf')
part.add_header('Content-Disposition', 'attachment', filename=filename)
msg.attach(part)
msg['To'] = ""
server.send_message(msg)
server.quit()
Raw email:
Received: from [127.0.0.1] (unknown [101.81.225.242])
by smtp8 (Coremail) with SMTP id DMCowABH3zUeOgBZsU+uAg--.2242S2;
Wed, 26 Apr 2017 14:11:42 +0800 (CST)
Content-Type: multipart/; boundary="===============4516509904929376112=="
MIME-Version: 1.0
From:
Subject: titlesub
To:
X-CM-TRANSID:DMCowABH3zUeOgBZsU+uAg--.2242S2
Message-Id:<59003A1E.C4DB82.14752#m12-12.163.com>
X-Coremail-Antispam: 1Uf129KBjDUn29KB7ZKAUJUUUUU529EdanIXcx71UUUUU7v73
VFW2AGmfu7bjvjm3AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxUkebkUUUUU
X-Originating-IP: [101.81.225.242]
Date: Wed, 26 Apr 2017 14:11:42 +0800 (CST)
X-CM-SenderInfo: pix130tbbsiiqu6rljoofrz/1tbivh7F0FZcM5OV1wAAsd
--===============4516509904929376112==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Type: application/pdf
Content-Disposition: attachment; filename="data.txt"
77u/
--===============4516509904929376112==--
If you look at the documentation for the MIMEApplication class, you should be passing the mime type in the constructor, not adding it as a separate header.
part = MIMEApplication(open("file.pdf", 'rb').read(), 'pdf')
filename="file.pdf"
part.add_header('Content-Disposition', 'attachment', filename=filename)

Not all MIME text showed on mail server

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()

Categories

Resources