I am having problems understanding how to email an attachment using Python. I have successfully emailed simple messages with the smtplib
. Could someone please explain how to send an attachment in an email. I know there are other posts online but as a Python beginner I find them hard to understand.
-
6here's a simple implementation that can attach multiple files, and even refer to them in the case of images to embed. http://datamakessense.com/easy-scheduled-emailing-with-python-for-typical-bi-needs/ – AdrianBR Feb 08 '16 at 18:50
-
I found this useful https://www.drupal.org/project/mimemail/issues/911612 turns out image attachments need to be attached to a related type child part. If you attach the image to the root MIME part the images can show up in the attached items list, and previewed in clients like outlook365. – Hinchy Nov 29 '19 at 11:12
-
@AdrianBR what if I have an image as a PDF file. Png's have pixel issues when zooming so png's are not good for me. – Charlie Parker Apr 24 '20 at 21:16
13 Answers
Here's another:
import smtplib
from os.path import basename
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
def send_mail(send_from, send_to, subject, text, files=None,
server="127.0.0.1"):
assert isinstance(send_to, list)
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 or []:
with open(f, "rb") as fil:
part = MIMEApplication(
fil.read(),
Name=basename(f)
)
# After the file is closed
part['Content-Disposition'] = 'attachment; filename="%s"' % basename(f)
msg.attach(part)
smtp = smtplib.SMTP(server)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
It's much the same as the first example... But it should be easier to drop in.
- 215,718
- 61
- 207
- 286
-
8Be careful with mutable defaults: http://stackoverflow.com/questions/101268/hidden-features-of-python/113198#113198 – Gringo Suave Mar 22 '11 at 06:09
-
1
-
11@user589983 Why not suggest an edit like any other user here would? I've changed the remaining reference to `file` into `f`. – Oli May 16 '11 at 22:26
-
10Notice for Python3 developers: module "email.Utils" has been renamed to "email.utils" – gecco Nov 11 '11 at 08:11
-
8for python2.5+ it's easier to use [MIMEApplication](http://docs.python.org/2/library/email.mime.html#email.mime.application.MIMEApplication) instead - reduces the first three lines of t he loop to: `part = MIMEApplication(open(f, 'rb').read())` – mata Jul 03 '13 at 12:01
-
Looks good. I just wanted to add 1 thing. I'm using procmail to filter my emails and when I encounter this email procmail had trouble figuring out the attachment name. I added the following to the attachment header and it works now: part.add_header('Content-Type', 'text/plain; name="%s"' % os.path.basename(f)) Any clarification on why this is needed for something like procmail would be appreciated. I'll see what I can find myself as well. – radtek Jun 03 '14 at 23:36
-
2Certain mail clients (Thunderbird in my case) don't correctly interpret "*.txt" attachments sent via this method. To correct this, just change `MIMEBase('application', 'octet-stream')` to `MIMEBase('application', 'base64')`. Please edit the answer to reflect this. – whereswalden Jul 01 '14 at 17:37
-
1in the method definition `file` should be by default equal to `None` because right now it's a pointer to same object within memory. https://stackoverflow.com/questions/6435793/optional-parameters-in-python-functions-and-their-default-values – JackLeo Oct 16 '14 at 10:52
-
3The Content-dispostion don't work see http://stackoverflow.com/a/3363538/128629 or http://stackoverflow.com/a/16509278/128629 for the correct way to do it – Xavier Combelle Dec 13 '14 at 21:12
-
@XavierCombelle Have you read the docs for `MIMEApplication`? You'll see the **_params argument that takes kwargs and converts them into attachments... This works. Tested it too. Here's the source where that happens: https://github.com/python-git/python/blob/master/Lib/email/mime/multipart.py – Oli Dec 14 '14 at 10:33
-
2@Oli the headers are bad: the part you missed " Additional parameters for the Content-Type header are taken from the keyword arguments (or passed into the _params argument)." for specifying a file name you want a new header – Xavier Combelle Dec 15 '14 at 21:20
-
This works for me usually but for some reason, the server disconnects sending a 834KB image. Appears to timeout. Is there any sort of "chunking" you have to do for larger attachments? Anyone else seeing this issue? – Jonathan Mar 06 '15 at 20:05
-
As others have noted, this is broken. Use the alternatives suggested by Xavier Combelle instead. Why this was accepted (an upvoted so much) is beyond me. Your attachments will be nameless!!! – John Chrysostom May 05 '15 at 19:29
-
2Yes - this is incorrect. You need to pass the headers as in pcboy's answer, even on Python 2. Also, I had to pass name='foo.pdf' to MIMEApplication in addition to Content_Disposition to get the filename to register properly in my email client (Gmail). Please edit the answer! – tobias.mcnulty Aug 02 '15 at 05:58
-
@tobias.mcnulty That did it. For future reference, *you* can edit things too :) – Oli Aug 02 '15 at 15:45
-
5Subject was not shown in the email sent. Worked only after changing the line to msg['Subject']=subject. I use python 2.7. – Luke Oct 29 '15 at 17:57
-
-
@Oli, I've edited this answer as I found it had some elements incorrect in my testing which seemed to be confirmed by the comments. While the comments and other answers here provided the solutions, it's important for this high profile (and accepted) question to be correct. Hope that's okay with you. – Peter Gibson May 27 '16 at 00:04
-
-
I would strongly suggest to default with TLS (encrypted) instead of plaintext: ``` server = smtplib.SMTP("smtp.gmail.com", 587) server.starttls() server.ehlo() server.login(gmail_user, gmail_pwd) ``` Rest is good, thanks for submit! – N0thing Nov 21 '16 at 05:58
-
-
@SaravananNandhan Just add a `cc` header field like we did with `To`. Python will pass these headers out whatever you specify but they might not send if your MTA doesn't understand them so use [standard header fields](http://jkorpela.fi/headers.html). `msg['cc'] = COMMASPACE.join(['example1@example.com', ...])`. – Oli Mar 09 '18 at 09:44
-
1@SaravananNandhan However, adding `msg['cc']` is not enough in case you really want to deliver the e-mail to them. You need to add them to the envelope too, such as; `smtp.sendmail(send_from, send_to+cc_to, msg.as_string())` considering cc_to is a list just as send_to. @Oli – highlytrainedbadger Nov 09 '20 at 08:07
-
Here is the modified version from Oli
for python 3
import smtplib
from pathlib import Path
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, message, files=[],
server="localhost", port=587, username='', password='',
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
files (list[str]): list of file paths to be attached to email
server (str): mail server host name
port (int): port number
username (str): server auth username
password (str): server auth password
use_tls (bool): use TLS mode
"""
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = COMMASPACE.join(send_to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach(MIMEText(message))
for path in files:
part = MIMEBase('application', "octet-stream")
with open(path, 'rb') as file:
part.set_payload(file.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition',
'attachment; filename="{}"'.format(Path(path).name))
msg.attach(part)
smtp = smtplib.SMTP(server, port)
if use_tls:
smtp.starttls()
smtp.login(username, password)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.quit()
- 1,348
- 12
- 17
-
thanks but it would be nice to also have the basic: the syntax for a single attached file (using it's path) – JinSnow Mar 03 '17 at 19:38
-
seems that you don't close your files, it will be garbage collected or closed at exit but it's bad habit. with open() as f: is the right way. – comte May 29 '17 at 21:26
-
Code does not work. Wrong variable name f in format(os.path.basename(f)) should be format(os.path.basename(path)) – Chris Jan 28 '18 at 22:25
-
1
-
2best answer for me but there is a small error: replace ```import pathlib```with ```from pathlib import Path``` – AleAve81 Apr 29 '20 at 20:38
-
-
Code does not work. `smtplib.SMTPDataError: (550, b'your mailer sends invalid headers')` – Andrey Sokolov Jan 25 '21 at 11:47
This is the code I ended up using:
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email import Encoders
SUBJECT = "Email Data"
msg = MIMEMultipart()
msg['Subject'] = SUBJECT
msg['From'] = self.EMAIL_FROM
msg['To'] = ', '.join(self.EMAIL_TO)
part = MIMEBase('application', "octet-stream")
part.set_payload(open("text.txt", "rb").read())
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="text.txt"')
msg.attach(part)
server = smtplib.SMTP(self.EMAIL_SERVER)
server.sendmail(self.EMAIL_FROM, self.EMAIL_TO, msg.as_string())
Code is much the same as Oli's post.
Code based from Binary file email attachment problem post.
-
2Good answer. Would be nice if it also contained code that adds a sample body text. – Steven Bluen Mar 17 '15 at 17:46
-
4Please note, in modern releases of the email library - the module imports are different. eg: `from email.mime.base import MIMEBase` – Varun Balupuri May 10 '19 at 10:27
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage
import smtplib
msg = MIMEMultipart()
msg.attach(MIMEText(file("text.txt").read()))
msg.attach(MIMEImage(file("image.png").read()))
# to send
mailer = smtplib.SMTP()
mailer.connect()
mailer.sendmail(from_, to, msg.as_string())
mailer.close()
Adapted from here.
- 215,718
- 61
- 207
- 286
-
Not quite what I am looking for. The file was sent as the body of an email. There is also missing brackets on line 6 and 7. I feel that we are getting closer though – Richard Jul 29 '10 at 13:23
-
2Emails are plain text, and that's what `smtplib` supports. To send attachments, you encode them as a MIME message and send them in a plaintext email. There's a new python email module, though: http://docs.python.org/library/email.mime.html – Katriel Jul 29 '10 at 13:33
-
@katrienlalex a working example would go a long way to help my understanding – Richard Jul 29 '10 at 13:52
-
-
1Are you sure the above example doesn't work? I don't have a SMTP server handy, but I looked at `msg.as_string()` and it certainly looks like the body of a MIME multipart email. Wikipedia explains MIME: http://en.wikipedia.org/wiki/MIME – Katriel Jul 29 '10 at 14:04
-
@katrielalex Thanks for the resource. Even though I have a working snip of code, I still am not sure whats happening here. This should help my understanding a bit. – Richard Jul 29 '10 at 14:34
-
1`Line 6, in
msg.attach(MIMEText(file("text.txt").read())) NameError: name 'file' is not defined` – Benjamin2002 Aug 10 '18 at 13:56
Another way with python 3 (If someone is searching):
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
fromaddr = "sender mail address"
toaddr = "receiver mail address"
msg = MIMEMultipart()
msg['From'] = fromaddr
msg['To'] = toaddr
msg['Subject'] = "SUBJECT OF THE EMAIL"
body = "TEXT YOU WANT TO SEND"
msg.attach(MIMEText(body, 'plain'))
filename = "fileName"
attachment = open("path of file", "rb")
part = MIMEBase('application', 'octet-stream')
part.set_payload((attachment).read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', "attachment; filename= %s" % filename)
msg.attach(part)
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(fromaddr, "sender mail password")
text = msg.as_string()
server.sendmail(fromaddr, toaddr, text)
server.quit()
Make sure to allow “less secure apps” on your Gmail account
- 765
- 11
- 25
Gmail version, working with Python 3.6 (note that you will need to change your Gmail settings to be able to send email via smtp from it:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from os.path import basename
def send_mail(send_from: str, subject: str, text: str,
send_to: list, files= None):
send_to= default_address if not send_to else send_to
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = ', '.join(send_to)
msg['Subject'] = subject
msg.attach(MIMEText(text))
for f in files or []:
with open(f, "rb") as fil:
ext = f.split('.')[-1:]
attachedfile = MIMEApplication(fil.read(), _subtype = ext)
attachedfile.add_header(
'content-disposition', 'attachment', filename=basename(f) )
msg.attach(attachedfile)
smtp = smtplib.SMTP(host="smtp.gmail.com", port= 587)
smtp.starttls()
smtp.login(username,password)
smtp.sendmail(send_from, send_to, msg.as_string())
smtp.close()
Usage:
username = 'my-address@gmail.com'
password = 'top-secret'
default_address = ['my-address2@gmail.com']
send_mail(send_from= username,
subject="test",
text="text",
send_to= None,
files= # pass a list with the full filepaths here...
)
To use with any other email provider, just change the smtp configurations.
- 555
- 6
- 7
The simplest code I could get to is:
#for attachment email
from django.core.mail import EmailMessage
def attachment_email(request):
email = EmailMessage(
'Hello', #subject
'Body goes here', #body
'MyEmail@MyEmail.com', #from
['SendTo@SendTo.com'], #to
['bcc@example.com'], #bcc
reply_to=['other@example.com'],
headers={'Message-ID': 'foo'},
)
email.attach_file('/my/path/file')
email.send()
It was based on the official Django documentation
- 1,093
- 12
- 15
-
3in your case you have to install django to send an email... it doesn't reply properly the question – comte May 29 '17 at 21:25
-
-
6@Auspex that's my point ;-) it's like installing LibreOffice to edit a config file... – comte Nov 02 '17 at 09:10
-
I find this helpful and informative. only the one module is imported, and its use is quite simple and elegant compared to the MIME hoops others jump through. In your example, by contrast, LibreOffice is more difficult to use than notepad. – 3pitt Sep 18 '19 at 17:13
Other answers are excellent, though I still wanted to share a different approach in case someone is looking for alternatives.
Main difference here is that using this approach you can use HTML/CSS to format your message, so you can get creative and give some styling to your email. Though you aren't enforced to use HTML, you can also still use only plain text.
Notice that this function accepts sending the email to multiple recipients and also allows to attach multiple files.
I've only tried this on Python 2, but I think it should work fine on 3 as well:
import os.path
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
def send_email(subject, message, from_email, to_email=[], attachment=[]):
"""
:param subject: email subject
:param message: Body content of the email (string), can be HTML/CSS or plain text
:param from_email: Email address from where the email is sent
:param to_email: List of email recipients, example: ["a@a.com", "b@b.com"]
:param attachment: List of attachments, exmaple: ["file1.txt", "file2.txt"]
"""
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_email
msg['To'] = ", ".join(to_email)
msg.attach(MIMEText(message, 'html'))
for f in attachment:
with open(f, 'rb') as a_file:
basename = os.path.basename(f)
part = MIMEApplication(a_file.read(), Name=basename)
part['Content-Disposition'] = 'attachment; filename="%s"' % basename
msg.attach(part)
email = smtplib.SMTP('your-smtp-host-name.com')
email.sendmail(from_email, to_email, msg.as_string())
I hope this helps! :-)
- 662
- 5
- 10
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
import mimetypes
import email.mime.application
smtp_ssl_host = 'smtp.gmail.com' # smtp.mail.yahoo.com
smtp_ssl_port = 465
s = smtplib.SMTP_SSL(smtp_ssl_host, smtp_ssl_port)
s.login(email_user, email_pass)
msg = MIMEMultipart()
msg['Subject'] = 'I have a picture'
msg['From'] = email_user
msg['To'] = email_user
txt = MIMEText('I just bought a new camera.')
msg.attach(txt)
filename = 'introduction-to-algorithms-3rd-edition-sep-2010.pdf' #path to file
fo=open(filename,'rb')
attach = email.mime.application.MIMEApplication(fo.read(),_subtype="pdf")
fo.close()
attach.add_header('Content-Disposition','attachment',filename=filename)
msg.attach(attach)
s.send_message(msg)
s.quit()
For explanation, you can use this link it explains properly https://medium.com/@sdoshi579/to-send-an-email-along-with-attachment-using-smtp-7852e77623
- 31
- 4
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.text import MIMEText
import smtplib
msg = MIMEMultipart()
password = "password"
msg['From'] = "from_address"
msg['To'] = "to_address"
msg['Subject'] = "Attached Photo"
msg.attach(MIMEImage(file("abc.jpg").read()))
file = "file path"
fp = open(file, 'rb')
img = MIMEImage(fp.read())
fp.close()
msg.attach(img)
server = smtplib.SMTP('smtp.gmail.com: 587')
server.starttls()
server.login(msg['From'], password)
server.sendmail(msg['From'], msg['To'], msg.as_string())
server.quit()
- 29
- 1
-
2hi, Welcome, Please always post an explanation of your answer when answering a question for better understanding – Ali Jan 03 '19 at 07:06
Below is combination of what I've found from SoccerPlayer's post Here and the following link that made it easier for me to attach an xlsx file. Found Here
file = 'File.xlsx'
username=''
password=''
send_from = ''
send_to = 'recipient1 , recipient2'
Cc = 'recipient'
msg = MIMEMultipart()
msg['From'] = send_from
msg['To'] = send_to
msg['Cc'] = Cc
msg['Date'] = formatdate(localtime = True)
msg['Subject'] = ''
server = smtplib.SMTP('smtp.gmail.com')
port = '587'
fp = open(file, 'rb')
part = MIMEBase('application','vnd.ms-excel')
part.set_payload(fp.read())
fp.close()
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment', filename='Name File Here')
msg.attach(part)
smtp = smtplib.SMTP('smtp.gmail.com')
smtp.ehlo()
smtp.starttls()
smtp.login(username,password)
smtp.sendmail(send_from, send_to.split(',') + msg['Cc'].split(','), msg.as_string())
smtp.quit()
- 101
- 2
- 4
With my code you can send email attachments using gmail you will need to:
set your gmail address at "YOUR SMTP EMAIL HERE"
set your gmail account password at "YOUR SMTP PASSWORD HERE_"
In the ___EMAIL TO RECEIVE THE MESSAGE_ part you need to set the destination email address.
Alarm notification is the subject,
Someone has entered the room, picture attached is the body
["/home/pi/webcam.jpg"] is an image attachment.
#!/usr/bin/env python
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
import os
USERNAME = "___YOUR SMTP EMAIL HERE___"
PASSWORD = "__YOUR SMTP PASSWORD HERE___"
def sendMail(to, subject, text, files=[]):
assert type(to)==list
assert type(files)==list
msg = MIMEMultipart()
msg['From'] = USERNAME
msg['To'] = COMMASPACE.join(to)
msg['Date'] = formatdate(localtime=True)
msg['Subject'] = subject
msg.attach( MIMEText(text) )
for file in files:
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)
server = smtplib.SMTP('smtp.gmail.com:587')
server.ehlo_or_helo_if_needed()
server.starttls()
server.ehlo_or_helo_if_needed()
server.login(USERNAME,PASSWORD)
server.sendmail(USERNAME, to, msg.as_string())
server.quit()
sendMail( ["___EMAIL TO RECEIVE THE MESSAGE__"],
"Alarm notification",
"Someone has entered the room, picture attached",
["/home/pi/webcam.jpg"] )
- 1
- 1
-
Long time no see! Good to see that you're properly attributing your code and including it directly in the answer. However, it's generally frowned upon to copy-paste the same answer code on multiple questions. If they *really* can be solved with the same solution, you should [flag the questions as duplicates](https://stackoverflow.com/help/privileges/flag-posts) instead. – Das_Geek Dec 11 '19 at 21:52
You can also specify the type of attachment you want in your e-mail, as an example I used pdf:
def send_email_pdf_figs(path_to_pdf, subject, message, destination, password_path=None):
## credits: http://linuxcursor.com/python-programming/06-how-to-send-pdf-ppt-attachment-with-html-body-in-python-script
from socket import gethostname
#import email
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import json
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
with open(password_path) as f:
config = json.load(f)
server.login('me@gmail.com', config['password'])
# Craft message (obj)
msg = MIMEMultipart()
message = f'{message}\nSend from Hostname: {gethostname()}'
msg['Subject'] = subject
msg['From'] = 'me@gmail.com'
msg['To'] = destination
# Insert the text to the msg going by e-mail
msg.attach(MIMEText(message, "plain"))
# Attach the pdf to the msg going by e-mail
with open(path_to_pdf, "rb") as f:
#attach = email.mime.application.MIMEApplication(f.read(),_subtype="pdf")
attach = MIMEApplication(f.read(),_subtype="pdf")
attach.add_header('Content-Disposition','attachment',filename=str(path_to_pdf))
msg.attach(attach)
# send msg
server.send_message(msg)
inspirations/credits to: http://linuxcursor.com/python-programming/06-how-to-send-pdf-ppt-attachment-with-html-body-in-python-script
- 13,522
- 35
- 118
- 206