Email and smtplib modules in Python - some basic emailing questions - python

I've just started learning Python and I'm trying to code a bot where it emails a HTML message along with a .docx (Microsoft Word) attachment. Here's my code and it works fine, but I'm confused with a few of the parameters.
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
import os
#Email Variables
myemail = "myemail#hotmail.com"
recipient = "recipientemail#hotmail.com"
#Specifying MIME variables to include in email header
mime_details = MIMEMultipart('mixed')
mime_details['Subject'] = "This is my email subject!"
mime_details['From'] = myemail
mime_details['To'] = recipient
#Creating body of message
html_body = """\
<html>\
<p>\
My email message!
</p>\
</html>\
"""
#Attaching File To Email
os.chdir('C:\\Users\Kyle\Desktop\')
openfile = open('testfile.docx', 'rb')
doc = MIMEApplication(openfile.read(), _subtype='vnd.openxmlformats-officedocument.wordprocessingml.document')
doc.add_header('Content-Disposition', 'attachment', filename='My Content.docx')
mime_details.attach(doc)
openfile.close()
#Recording MIME types of email parts
htmlpart = MIMEText(html_body, _subtype='html')
#Attaching parts to message container
mime_details.attach(htmlpart)
#Sending message via SMTP server
s = smtplib.SMTP(host='smtp.live.com', port=587)
s.ehlo()
s.starttls()
s.login(myemail, 'password')
s.sendmail(myemail, recipient, mime_details.as_string())
s.quit()
I have a few questions regarding the code above, and I would greatly appreciate it if you can help me clear whatever confused thoughts I have regarding the modules above.
A) The add_header module
mime_details['Subject'] = "This is my email subject!"
mime_details['From'] = myemail
mime_details['To'] = recipient
As you can see from the snippet above, I clearly defined each header variable in the MIMEMultipart envelope. According to Python's docs, these values will be added to the header directly. I tried using the add_header() method directly as an alternative, something like:
mime_details.add_header(Subject = "This is my email subject!", To = recipient, From = myemail)
But it gave me errors. Any idea why?
B) At the last few lines of my code, I had to append a .as_string() to the payload I was sending. I tried taking it out but it gave me a TypeError: expected string or buffer.
The Python docs give me this:
as_string(unixfrom=False, maxheaderlen=0, policy=None)
Return the entire message flattened as a string.
I assume that I have to append this in every case? the mime_details object has two parts to it - the HTML message and the .docx file. Both will be converted to strings? What about image files?
C) When I open my file to attach, I read it in 'rb' mode.
Here's the code:
openfile = open('testfile.docx', 'rb')
I tried using 'r' but it raises an error. I've read up on this and 'rb' mode simply opens the file in binary. Why would it make sense to open a .docx file in binary mode? Am I to use 'rb' mode for ALL non.txt files?
D) Finally, I've seen some people use base64 encoding for attachments. I've searched around and a particularly popular script over here is below (from How to send email attachments with Python):
import smtplib, os
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
from email import encoders
def send_mail( send_from, send_to, subject, text, files=[], server="localhost", port=587, username='', password='', isTls=True):
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime = True)
msg['Subject'] = subject
msg.attach( MIMEText(text) )
for f in files:
part = MIMEBase('application', "octet-stream")
part.set_payload( open(f,"rb").read() )
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="{0}"'.format(os.path.basename(f)))
msg.attach(part)
smtp = smtplib.SMTP(server, port)
if isTls: smtp.starttls()
smtp.login(username,password)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.quit()
Why does content need to be encoded in base64? Does this apply to all files and is a general thing to be added, or can I ignore this? What situations am I to invoke that method?
Finally, being a noob at Python, some of my code above may be redundant\badly structured. If you have any tips\suggestions on how I can improve my simply script, please do let me know.
Thank you!

Related

How can I embed an image into an email message?

and thanks in advance for helping out. I've been trying to embed an image on this specific code. I've followed some other posts related to this but the result hasn't shown the image displayed when the email is opened. It just shows it as an attachment. Any guidance will be more than helpful. Thanks again!
import email, smtplib, ssl
import os
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
#body = """ Attached are your results!
#Feel free to reply to this message if you have any questions.
# We look forward to working with you soon.
# Thank you for using Weckrogrid-20! """
sender_email = "WeckroGrid-20"
receiver_email = "*****#gmail.com"
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = 'WeckroGrid-20'
#message["To"] = '*****#gmail.com'
message["Subject"] = 'Your Microgrid Plan is here!'
#message["Bcc"] = 'receiver_email' # Recommended for mass emails
# Add body to email
#message.attach(MIMEText(body, "plain"))
filename = "dummy.pdf" # In same directory as script
# Open PDF file in binary mode
with open(filename, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
f"attachment; filename= {filename}",
)
#Add attachment to message and convert message to string
message.attach(part)
text = message.as_string('')
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login('******#gmail.com', '*******')
server.sendmail(sender_email, receiver_email, text)

Is there a way to send email with a dataframe attachment?

I currently have a script that manipulates a .csv file using pandas. I'm trying to send a MIMEMultipart email with the latest .csv version of the file that has been amended but for some reason, the email recipients keep receiving an older unaltered version of the .csv that I trying to send. I'm trying to make sense of it in my head because the old version of the .csv file is written over before it is sent but the original version of the .csv is sent to the recipients.
Maybe I need to specify a path for smtplib to get the file as opposed to just giving the name of the file. Is there a way to do that or is there another way around my problem? I've already tried to change the name to something else in order for smtplib to be able to differentiate between the old .csv and the new one.
This doesn't work though as the file is placed in the directory but my script says that the new file doesn't exist
This is my current code:
email_user = 'Bot#gmail.com'
email_password = 'Business101'
email_send = ('myemail#gmail.com', 'myfriendsemail#gmail.com')
subject = 'TOP 5 CONTRACTS'
msg = MIMEMultipart()
msg['From'] = email_user
msg['To'] = ",".join(email_send)
msg['Subject'] = subject
body = 'These are the latest contracts for this week!'
msg.attach(MIMEText(body,'plain'))
filename='CC.csv'
attachment =open(filename,'rb')
part = MIMEBase('application','octet-stream')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition',"attachment; filename= "+filename)
msg.attach(part)
text = msg.as_string()
server = smtplib.SMTP('smtp.gmail.com',587)
server.starttls()
server.login(email_user,email_password)
server.sendmail(email_user,email_send,text)
server.quit()
print("Emailed Recipients")
Might be worth mentioning that this process is an automated one so the script is being run from a Unix Executable file on my mac.
If you can assist, I'd really appreciate it!
This has been the best way to do it, thanks
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from smtplib import SMTP
import smtplib
import sys
import pandas as pd
df_test = pd.read_csv('/Users/emmanuelafoke/Documents/Selenium/CC.csv')
email_user = 'myemailaddress#gmail.com'
email_password = 'mypassword'
recipients = ['theiremailaddress#gmail.com']
emaillist = [elem.strip().split(',') for elem in recipients]
msg = MIMEMultipart()
msg['Subject'] = 'SUBJECT'
msg['From'] = 'myemailaddress#gmail.com'
html = """\
<html>
<head></head>
<body>
{0}
</body>
</html>
""".format(df_test.to_html())
part1 = MIMEText(html, 'html')
msg.attach(part1)
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(email_user,email_password)
server.sendmail(msg['From'], emaillist , msg.as_string())
Thanks for all of your help!
You could actually do this by using the following libraries: email.mime.application, MIMEApplication, email.mime.multipart and email.mime.text. Now, I don't know which client you are using and you might have to do some adjustments to this.
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
SEND_FROM = 'myaddress#client.com' ## Your email address
TOSEND = {'dataframe.csv': export_csv, 'dataframe.xlsx': export_my_excel}
def send_dataframe(send_to, subject, body, df):
multipart = MIMEMultipart()
multipart['From'] = SEND_FROM
multipart['To'] = send_to
multipart['Subject'] = subject
for filename in EXPORTERS:
attachment = MIMEApplication(TOSEND[filename](df)) ### Here is where the df is attached
attachment['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
multipart.attach(attachment)
multipart.attach(MIMEText(body, 'html'))
s = smtplib.SMTP('localhost')
s.sendmail(SEND_FROM, send_to, multipart.as_string())
s.quit()
In send_dataframe() you'll have to put the df you want to attach. This is where TOSEND come in. As you see, there is a function called export_my_excel. You can create it as
import io
import pandas as pd
def export_my_excel(df):
with io.BytesIO() as buffer:
writer = pd.ExcelWriter(buffer)
df.to_excel(writer)
writer.save()
return buffer.getvalue()

Python: sending mail via python creates unknown attachment

I'm trying to send a mail + attachment (.pdf file) via python.
The mail is send but the attachment becomes an unknown attachment instead of being a .pdf file
My code looks like this:
import smtplib
import os
import ssl
import email
from email import encoders
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
port = 465
smtp_server = "smtp.gmail.com"
subject = "An example of txt.file"
sender = "..."
receiver = "..."
password = "..."
message = MIMEMultipart()
message["From"] = sender
message["To"] = receiver
message["Subject"] = subject
filename = '318.pdf'
attachment = open(filename, "rb")
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
part.add_header('Content Disposition', 'attachment', filename=filename)
encoders.encode_base64(part)
message.attach(part)
message.attach(part)
body = "This is an example of how to send an email with an .pdf-attachment."
message.attach(MIMEText(body, 'plain'))
text = message.as_string()
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
server.login(sender, password)
server.sendmail(sender, receiver, text)
print('Sent')
What is wrong with it or what do I have to do differently?
I've tried different file types, the .pdf file is in the python file directory,...
You wrote
part.add_header('Content Disposition', 'attachment', filename=filename)
But the 'filename' argument-name must be provided as a string and you also missed the hyphen in 'Content-Disposition'. Try
part.add_header('Content-Disposition', 'attachment; filename=' + filename)
This should solve your issue. Just pointing out, you attached 'part' twice - on lines 20 and 22.
I think you might already be following this article. But if not, I think you'll find it useful.

Python: Sending email with attachment only with the stdlib?

I want to send an email with attachment (for example a text file) with python. Is this possible with the stdlib or do I have to download and install other packages?
I would like to do this with the stdlib.
thx. :)
You can try this:
# Import smtplib for the actual sending function
import smtplib
# Import the email modules we'll need
from email.mime.text import MIMEText
# Open a plain text file for reading. For this example, assume that
# the text file contains only ASCII characters.
fp = open(textfile, 'rb')
# Create a text/plain message
msg = MIMEText(fp.read())
fp.close()
# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you
# Send the message via our own SMTP server.
s = smtplib.SMTP('localhost')
s.send_message(msg)
s.quit()

Python mail: encoded attachments are truncated

I'm using the following function to send an email message with two attachments in my python script:
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
...
def sendMail(sender_name, to, subject, text, files=None,server="localhost"):
assert type(to)==list
if files:
assert type(files)==list
print "Files: ",files
fro = sender_name
msg = MIMEMultipart()
msg['From'] = fro
msg['To'] = COMMASPACE.join(to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach( MIMEText(text) )
if files:
for file in files:
# ************** File attaching - Start **************
part = MIMEBase('application', "octet-stream")
part.set_payload( open(file,"rb").read() )
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
msg.attach(part)
# ************** File attaching - End **************
server.set_debuglevel(1)
server.ehlo()
server.starttls()
server.ehlo()
server.sendmail(fro, to, msg.as_string())
server.quit()
I get the mail, and the attachments are there, but for some reason, they are truncated a bit. My guess is I'm missing something in the encoding process.
For example:
Attachment 1: Original file byte count is 1433902, while the new byte count is 1433600
Attachment 2: Original file byte count is 2384703, while the new byte count is 2383872
Any ideas?
Found the problem. Turns out I tried sending the files before the buffer of the writing process was fully flushed.
So, it was a synchronization issue and not an encoding issue.
Sorry about that, and thanks for the help guys!
Could it be related to your current base64.MAXBINSIZE? Encoders.encode_base64 uses base64.encodestring internally. The default value for base64.MAXBINSIZE is 57, can always try setting it larger: base64.MAXBINSIZE = 65536
If the file is already written--be sure to .close() the file and re-open()/.read() it for the payload.
My issues stemmed from timing and this solved the issue for me.

Categories