I'm trying to attach a PDF file to an email like this:
def send_email(gmail, password, post_description,
reply_email, attachment_path, email_body_path):
msg = MIMEMultipart()
with open(email_body_path) as f:
msg.attach(MIMEText(f.read()))
if attachment_path != None:
with open(attachment_path, 'rb') as f:
msg.attach(MIMEApplication(
f.read(),
Content_Disposition='attachment, filename="%s"' % basename(f)))
smtp = smtplib.SMTP('smtp.gmail.com',587)
smtp.login(gmail, password)
smtp.sendmail(gmail, 'address#gmail.com', msg.as_string()
The PDF is attached and has the correct title, but the contents are always "ECO" and nothing else.
See email examples on how to make attachments correctly:
outer = MIMEMultipart()
<...>
fp = open(path, 'rb')
msg = MIMEAplication(fp.read(), _subtype=subtype) #or another appropriate subclass
#some subclasses have additional parameters
<or>
msg = MIMEBase(maintype, subtype)
msg.set_payload(fp.read())
encoders.encode_base64(msg)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
outer.attach(msg)
To add text to the message, you need to attach a MIMEText as well as the PDF as per multipart/alternative example (do not forget to specify encoding if it's not the RFC-dictated default, iso-8859-1):
part = MIMEText(<text>, _charset='<charset>')
outer.attach(part)
So, you were doing everything correctly except forming the MIMEApplication. MIMEBase.__init__ source code shows that extra parameters on the constructor call all go into the Content-Type header.
Related
I have a list of text files and html files generated by two distinct functions. Each file is labeled signal1.txt, signal2, etc. and signal1.html, signal2.html, etc. I need to send an email with each file pair (signal1.txt and signal1.html, signal2.txt and signal.2.html, and so forth). I've tried several different ways, but I keep getting just one file pair attached (the last file number whatever it is). I have no problem sending one file type, but it gets messy when I try with two different files.
Any help is appreciated. The code is as follows.
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
import smtplib, ssl
import os
dirname = r'C:\Path\To\Files'
ext = ('.txt','html')
for files in os.scandir(dirname):
if files.path.endswith(ext):
def sendmail():
html_body = '''
<html>
<body>
<p style="font-size: 12;"> <strong>Alert</strong><br>{html}</p>
</body>
</html>
'''.format(html=html)
subject = f'Text file content'
senders_email = 'mail#mail.com'
receiver_email = 'mail#mail.com'
# Create a multipart message and set headers
message = MIMEMultipart('alternative')
message['From'] = senders_email
message['To'] = receiver_email
message['Subject'] = subject
#Attach email body
message.attach(MIMEText(html_body, 'html'))
# Name of the file to be attached
filename = f'signal.html'
# Open file in binary mode
with open(filename, 'rb') as attachment:
# Add file as application/octet-stream
part = MIMEBase('application', 'octet-stream')
part.set_payload(attachment.read())
# Encodes file in ASCII characters to send via 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 into server using secure connection
context = ssl.create_default_context()
with smtplib.SMTP("smtp.mail.com", 25) as server:
# server.starttls(context=context)
# server.login(senders_email, 'password')
server.sendmail(senders_email, receiver_email, text)
print("Email sent!")
sendmail()
I've adapted one of these examples for your problem. This puts all the files in one email:
# Import smtplib for the actual sending function.
import smtplib
# Here are the email package modules we'll need.
from email.message import EmailMessage
import os
dirname = 'C:\Path\To\Files'
ext = ('.txt','html')
msg = EmailMessage()
msg['Subject'] = 'Text file content'
msg['From'] = 'mail#mail.com'
msg['To'] = 'mail#mail.com'
# Open the files in binary mode. You can also omit the subtype
# if you want MIMEImage to guess it.
for filename in os.scandir(dirname):
if filename.path.endswith(ext):
with open(filename, 'rb') as fp:
data = fp.read()
msg.add_attachment(data)
# Send the email via our own SMTP server.
with smtplib.SMTP('localhost') as s:
s.send_message(msg)
Found a way that worked using glob.
dirname = r'C:\Path\To\Files'
regex = create_txt_files(event_dict)
html_file = create_html_files(event_dict)
signalfiles = sorted(list(pathlib.Path(dirname).glob('*.txt')))
htmlfiles = sorted(list(pathlib.Path(dirname).glob('*.html')))
for i, path_html_file in enumerate(htmlfiles):
sendmail(html_file[i], regex[i], path_html_file)
I am using python to generate and send email messages. I want the messages to include both
embedded images (this post shows how)
attachments
Currently, the code below does all of this, except that the attachments are not 100% showing up. In Outlook, there is no way to see them. In Gmail, when you see the mail in your inbox it seems to have no attachment, but when you open the mail, there is the attachment at the bottom.
How can I get the attachments to show up consistently?
Following is my current code.
I'll also show below that a comparison between the header seem from my gmail inbox of an email sent with this, vs one where everything works as expected.
import smtplib
from os import getcwd, path
from config import mailer_settings
from email.message import EmailMessage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from email.utils import formatdate
from mimetypes import guess_type
def send_mail(send_from, send_to, subject, message, message_html="", file_loc: str = None, locs=[], cids=[],
use_tls=True):
"""Compose and send email with provided info and attachments.
Args:
send_from (str): from name
send_to (list[str]): to name(s)
subject (str): message title
message (str): message body
message_html: message body with html
file_loc (list[str]): path of file to be attached to email
locs: list[str]: list of file locations for embedded images
cids: list: list of generated (email.utils.make_msgid) cids for embedded images
use_tls (bool): use TLS mode
"""
# --- Method 1: can't get embedded pictures to work ---
# msg = MIMEMultipart('alternative')
# part1 = MIMEText(message, 'plain')
# part2 = MIMEText(message_html, 'html')
# msg.attach(part1)
# msg.attach(part2)
# --- Method 2 currently best ---
msg = EmailMessage()
msg.set_content(message)
msg.add_alternative(message_html, subtype='html')
# --- Method 3:
# from (https://stackoverflow.com/questions/35925969/python-sent-mime-email-attachments-not-showing-up-in-mail-live)
# not sure how to get it to work with embedded pictures, since MIMEImage is now deprecated ---
# Create the root message and fill in the from, to, and subject headers
b = False
if b:
msg = MIMEMultipart('related')
msg.preamble = 'This is a multi-part message in MIME format.'
# Encapsulate the plain and HTML versions of the message body in an
# 'alternative' part, so message agents can decide which they want to display.
msgAlternative = MIMEMultipart('alternative')
msg.attach(msgAlternative)
msgText = MIMEText('This is the alternative plain text message.')
msgAlternative.attach(msgText)
# We reference the image in the IMG SRC attribute by the ID we give it below
msgText = MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>Nifty!', 'html')
msgAlternative.attach(msgText)
# This example assumes the image is in the current directory
fp = open(getcwd() + '\\mail\\example_pdf.ong', 'rb')
# :( MIMEImage is deprecated
# msgImage = MIMEImage(fp.read())
fp.close()
# Define the image's ID as referenced above
# msgImage.add_header('Content-ID', '<image1>')
# msg.attach(msgImage)
# --- end Method 3 ---
msg['From'] = send_from
msg['To'] = send_to
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
# now open the image and attach it to the email
for loc, cid in zip(locs, cids):
with open(loc, 'rb') as img:
# know the Content-Type of the image
maintype, subtype = guess_type(img.name)[0].split('/')
# attach it
msg.get_payload()[1].add_related(img.read(), maintype=maintype, subtype=subtype, cid=cid)
if file_loc:
mimetype, encoding = guess_type(file_loc)
mimetype = mimetype.split('/', 1)
fp = open(file_loc, 'rb')
attachment = MIMEBase(mimetype[0], mimetype[1])
attachment.set_payload(fp.read())
fp.close()
encoders.encode_base64(attachment)
attachment.add_header('Content-Disposition', 'attachment',
filename=path.basename(file_loc))
msg.attach(attachment)
server = mailer_settings["server"]
port = mailer_settings["port"]
username = mailer_settings["username"]
password = mailer_settings["password"]
smtp = smtplib.SMTP(server, port)
if use_tls:
smtp.starttls()
smtp.login(username, password)
smtp.sendmail(send_from, send_to, msg.as_string())
print("message sent!")
smtp.quit()
The Header in my emails:
--===============0780945104962264622==
Content-Type: application/pdf
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="Ecoemballage - test_product_01a.pdf"
--===============0780945104962264622==--
Header from a Microsoft email that is showing it attachment like it should:
--=-FabH5jV6gYAblpCdlHoDfQ==
Content-Type: application/octet-stream; name=221885190882013.pdf
Content-Disposition: attachment; filename=221885190882013.pdf
Content-Transfer-Encoding: base64
--=-FabH5jV6gYAblpCdlHoDfQ==--
I found the answer here.
The problem here is that you have directly attached a pre-built MIME
part to a multipart/alternative message. It ends in an incorrect
message that may be poorly processed by mail readers.
In the email.message interface, you should use the add_attachement
method. It will handle the base64 encoding and will change the message
into a multipart/mixed one:
To fix, I changed my code as follows:
if file_loc:
fp = open(file_loc, 'rb')
filename = path.basename(file_loc)
# mimetype, encoding = guess_type(file_loc)
# mimetype = mimetype.split('/', 1)
# attachment = MIMEBase(mimetype[0], mimetype[1])
# attachment.set_payload(fp.read())
# fp.close()
# encoders.encode_base64(attachment)
# attachment.add_header('Content-Disposition', 'attachment',
# filename=path.basename(file_loc))
# msg.attach(attachment)
msg.add_attachment(fp.read(), maintype='application',
subtype='octet-stream', filename=filename)
I want to write a function to attach a csv file to an E-Mail with HTML text. Everything works fine. I sends the E-Mail with the text and the attachment with the right information. Only the data format is wrong. I tried different varieties in MIMEBASE('application', 'octet-stream'). This one gives me a '.bin' data which I cannot open on macOS. Others gave me a plain text data which included the right data, but I don't wanna copy it manually into a csv. Does someone have a solution how I get out the data as '.csv'? Most of the solutions I found here just looked like my code below.
Code:
def send_mail(receiver, subject, filepath, attachname):
#login information
port = 123
smtp_server = "test.server.com"
login = "testlogin" # your login
password = "12345678910" # your password
# specify the sender’s and receiver’s email addresses and text in HTML
sender = "testmail#test.com"
receiver = receiver
message = MIMEMultipart()
message["Subject"] = subject
message["From"] = sender
message["To"] = COMMASPACE.join([receiver])
html_text = """\
<html>
<body>
<p>Hi ,<br>
<p>kind regards</p>
<p> This is an automatized Mail </p>
</body>
</html>
"""
# convert both parts to MIMEText objects and add them to the MIMEMultipart message
text = MIMEText(html_text, "html")
message.attach(text)
#add a file as attachment
file = open(filepath, "rb")
att = MIMEBase('application', 'octet-stream')
att.set_payload(file.read())
encoders.encode_base64(att)
att.add_header("Content_Disposition",
'attachment', filename = attachname)
message.attach(att)
try:
#send your message with credentials specified above
with smtplib.SMTP(smtp_server, port) as server:
server.connect(smtp_server, port)
server.ehlo()
server.starttls()
server.ehlo()
server.login(login, password)
server.sendmail(sender, receiver, message.as_string())
# tell the script to report if your message was sent or which errors need to be fixed
print('Sent')
except (gaierror, ConnectionRefusedError):
print('Failed to connect to the server. Bad connection settings?')
except smtplib.SMTPServerDisconnected:
print('Failed to connect to the server. Wrong user/password?')
except smtplib.SMTPException as e:
print('SMTP error occurred: ' + str(e))
send_mail('testmail#test.com', 'TEST', '/Users/Changer/Desktop/test.csv', 'test.csv')
Email clients may use the content type of the attachment part to determine which program is used to open the attachment, so specifying a suitable content type may help.
This code uses the standard library's mimetypes module to try to guess the correct content type of the attachment, based on its name:
import mimetypes
mimetypes.init()
def send_mail(receiver, subject, filepath, attachname):
...
mimetype, _ = mimetypes.guess_type(attachname)
if mimetype is None:
mimetype = 'application/octet-stream'
type_, _, subtype = mimetype.partition('/')
att = MIMEBase(type_, subtype)
...
The above code will generate these headers for the attachment:
b'Content-Type: text/x-comma-separated-values'
b'MIME-Version: 1.0'
b'Content-Transfer-Encoding: base64'
b'Content_Disposition: attachment; filename="test.csv"'
Whether a mac will open a csv with a spreadsheet application (I assume this is what you want) depends on the configuration of the machine (See this blog for an example). If you want to be sure open the file in a spreadsheet it might be more effective to convert it to a spreadsheet format and send the spreadsheet.
I am currently trying to send a pdf attachment via Gmail API, but the file that I receive seems to be corrupted. Here is the code that I use to create the Email.
message_out = MIMEMultipart()
content_type, encoding = mimetypes.guess_type(attachment)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
main_type, sub_type = content_type.split('/', 1)
with open(attachment, 'rb') as fp:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
filename = os.path.basename(attachment)
msg.add_header('Content-Disposition', 'attachment', filename=filename)
msg.add_header('Content-Type', main_type, name=filename)
msg.add_header('Content-Transfer-Encoding', '7bit')
email.encoders.encode_base64(msg)
message_out.attach(msg)
return {'raw': base64.urlsafe_b64encode(message_out.as_bytes()).decode()}
When I try to open the attachment, I then get a "failed to load PDF document". I guess it has something to do with the encoding, but I cannot figure out why, I was thinking that email.encoders was going to solve all my problems. (same problem happens with png image)
Thank you very much,
Djazouli
As expected, this was an encoding issue, the line
msg.add_header('Content-Transfer-Encoding', '7bit')
shouldn't be there
I am sending mail from a linux box from a directory path which has three csv files. I want to attach all three in my email. Below is the script.
def mailer(sender, to, path):
msg = MIMEMultipart()
msg['Subject'] = 'UMR_EOD_RECONCILLATIONS'
msg['From'] = sender
msg['To'] = to
for file in os.listdir(path):
f = open( path + file, 'rb')
csv = MIMEText(f.read())
f.close()
msg.attach(csv)
mailer = smtplib.SMTP('localhost')
mailer.sendmail(sender,to, msg.as_string())
mailer.quit()
I have been scratching my head for a while and tried multiple times but still facing below issues.
The files which are attached are text files i.e. .txt extension, I want is to be csv
The files have funny names ATT00001.txt and ATT00002.txt which remains the same.
The third file is never attached to the mail it contents are outputted in the body and it's the damn same file however times I may try.
I have tried setting below but to no avail.
msg["Content-Disposition"] = "attachment; filename=" + file + ";"
msg.add_header('Content-Disposition', 'attachment', filename=file)
1) The first text object will be displayed as the email message. So, add an extra text object first.
2) CSV files should be transmitted as content-type: text/csv, not content-type: text/plain.
#UNTESTED
def mailer(sender, to, path):
msg = MIMEMultipart()
msg['Subject'] = 'UMR_EOD_RECONCILLATIONS'
msg['From'] = sender
msg['To'] = to
msg.attach(MIMEText('Here are the reports you asked for.'))
for file in os.listdir(path):
f = open( path + file, 'rb')
csv = MIMEText(f.read(), 'csv')
f.close()
csv.add_header('Content-Disposition', 'attachment', filename=file)
msg.attach(csv)
mailer = smtplib.SMTP('localhost')
mailer.sendmail(sender,to, msg.as_string())
mailer.quit()
I always advise to run from doing MIME stuff when you just want to send emails. I feel like no one wants to deal with that. It feels like Java.
Try yagmail; my apologies, I'm the developer.
Its purpose is to make it super easy to send emails with HTML, inline images and attachments.
The code for what you want:
import os
import yagmail
def mailer(sender, to, path):
yag = yagmail.SMTP(sender, host="localhost", smtp_skip_login=True)
contents = ['Here are the reports you asked for.'] + os.listdir(path)
yag.send(to, 'UMR_EOD_RECONCILLATIONS', contents)
I'd suggest to read the README for more nice tricks in there :)
To get started, use pip install yagmail to install yagmail.