307

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.

martineau
  • 99,260
  • 22
  • 139
  • 249
Richard
  • 12,790
  • 29
  • 77
  • 108
  • 6
    here'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 Answers13

453

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.

Oli
  • 215,718
  • 61
  • 207
  • 286
  • 8
    Be careful with mutable defaults: http://stackoverflow.com/questions/101268/hidden-features-of-python/113198#113198 – Gringo Suave Mar 22 '11 at 06:09
  • 1
    -1 The for loop will not work. Should read 'for file in files'. – garnertb May 16 '11 at 16:23
  • 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
  • 10
    Notice for Python3 developers: module "email.Utils" has been renamed to "email.utils" – gecco Nov 11 '11 at 08:11
  • 8
    for 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
  • 2
    Certain 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
  • 1
    in 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
  • 3
    The 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
  • 2
    Yes - 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
  • 5
    Subject 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
  • like @Luke I needed to use `msg['Subject']=subject` – Shadi Mar 11 '16 at 10:55
  • @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
  • Cyrillic is broken in the body. – antonavy Aug 24 '16 at 21:02
  • 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
  • @Oli how can we add CC – Saravanan Nandhan Mar 09 '18 at 09:37
  • @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
  • But you don't have a body for the email. – theerrormagnet Apr 08 '21 at 06:52
85

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()
Ehsan Iran-Nejad
  • 1,348
  • 12
  • 17
69

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.

vinzee
  • 10,965
  • 9
  • 34
  • 51
Richard
  • 12,790
  • 29
  • 77
  • 108
28
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.

Oli
  • 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
  • 2
    Emails 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
  • I guess I should add that I am using python 2.4 – Richard Jul 29 '10 at 13:52
  • 1
    Are 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
8

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

Sudarshan
  • 765
  • 11
  • 25
6

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.

Ferrarezi
  • 555
  • 6
  • 7
5

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

Andrade
  • 1,093
  • 12
  • 15
  • 3
    in 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
  • @comte 'coz python is only ever used for Django, right? – Auspex Nov 01 '17 at 09:21
  • 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
4

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

2
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

sdoshi
  • 31
  • 4
2
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()
  • 2
    hi, Welcome, Please always post an explanation of your answer when answering a question for better understanding – Ali Jan 03 '19 at 07:06
0

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()
TonyRyan
  • 101
  • 2
  • 4
0

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"] )
John Rua
  • 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
0

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

Charlie Parker
  • 13,522
  • 35
  • 118
  • 206