How do I send attachments using SMTP?
Asked Answered
C

6

26

I want to write a program that sends email using Python's smtplib. I searched through the document and the RFCs, but couldn't find anything related to attachments. Thus, I'm sure there's some higher-level concept I'm missing out on. Can someone clue me in on how attachments work in SMTP?

Catawba answered 27/12, 2009 at 14:31 Comment(3)
Just to be clear, there is nothing at all in SMTP to handle this, it's entirely handled by structuring the document being sent as a MIME document. The article on MIME on wikipedia seems to cover the basic pretty well.Carminacarminative
Including a link directly to the "email examples" section of the Python docs would make any answer complete: docs.python.org/library/email-examples.htmlArcane
I believe @PeterHansen's link moved to docs.python.org/3/library/email.examples.htmlRuck
L
14

What you want to check out is the email module. It lets you build MIME-compliant messages that you then send with smtplib.

Lepore answered 27/12, 2009 at 14:39 Comment(0)
K
35

Here is an example of a message with a PDF attachment, a text "body" and sending via Gmail.

# Import smtplib for the actual sending function
import smtplib

# For guessing MIME type
import mimetypes

# Import the email modules we'll need
import email
import email.mime.application

# Create a text/plain message
msg = email.mime.Multipart.MIMEMultipart()
msg['Subject'] = 'Greetings'
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'

# The main body is just another attachment
body = email.mime.Text.MIMEText("""Hello, how are you? I am fine.
This is a rather nice letter, don't you think?""")
msg.attach(body)

# PDF attachment
filename='simple-table.pdf'
fp=open(filename,'rb')
att = email.mime.application.MIMEApplication(fp.read(),_subtype="pdf")
fp.close()
att.add_header('Content-Disposition','attachment',filename=filename)
msg.attach(att)

# send via Gmail server
# NOTE: my ISP, Centurylink, seems to be automatically rewriting
# port 25 packets to be port 587 and it is trashing port 587 packets.
# So, I use the default port 25, but I authenticate. 
s = smtplib.SMTP('smtp.gmail.com')
s.starttls()
s.login('[email protected]','xyzpassword')
s.sendmail('[email protected]',['[email protected]'], msg.as_string())
s.quit()
Kampmann answered 23/11, 2011 at 13:32 Comment(8)
This solved my problem for emailing excel files as well which was awesome because it kept me out of os.system calling Ruby! Thanks Kevin!Kovar
This solution also worked for me, after creating a .xls file using the Python xlwt module. Instead of sending over Gmail I used my company's mail server. Thanks, and +1Courage
this method actually worked and is much cleaner imo!Flint
Works here! Thanks KevinDispersoid
adding the header (in python2.7.9) didn't name the file properly. My fix was to change the line to: att.add_header('Content-Disposition', 'attachment; filename=%s' % filename)Sekyere
Greetings from 2017! I'm running this code (with one modification: smtplib.SMTP('smtp.gmail.com', 587) ) and it seems that whenever I run it my connection speed drops to 1996 levels. It's really strange. Any advice? TIAHennery
(To clarify ^, the attachment itself is a very small file)Hennery
@Hennery - in my opinion, there can be no connection between running this and your connection speed. Maybe you have antivirus sniffing your outgoing emails.Kampmann
R
21

Here's an example I snipped out of a work application we did. It creates an HTML email with an Excel attachment.

  import smtplib,email,email.encoders,email.mime.text,email.mime.base

  smtpserver = 'localhost'
  to = ['[email protected]']
  fromAddr = '[email protected]'
  subject = "my subject"

  # create html email
  html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
  html +='"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">'
  html +='<body style="font-size:12px;font-family:Verdana"><p>...</p>'
  html += "</body></html>"
  emailMsg = email.MIMEMultipart.MIMEMultipart('alternative')
  emailMsg['Subject'] = subject
  emailMsg['From'] = fromAddr
  emailMsg['To'] = ', '.join(to)
  emailMsg['Cc'] = ", ".join(cc)
  emailMsg.attach(email.mime.text.MIMEText(html,'html'))

  # now attach the file
  fileMsg = email.mime.base.MIMEBase('application','vnd.ms-excel')
  fileMsg.set_payload(file('exelFile.xls').read())
  email.encoders.encode_base64(fileMsg)
  fileMsg.add_header('Content-Disposition','attachment;filename=anExcelFile.xls')
  emailMsg.attach(fileMsg)

  # send email
  server = smtplib.SMTP(smtpserver)
  server.sendmail(fromAddr,to,emailMsg.as_string())
  server.quit()
Retirement answered 27/12, 2009 at 16:3 Comment(1)
The multipart subtype should be 'mixed' instead of 'alternative' otherwise you won't see the attached file in some email clients.Intercession
L
14

What you want to check out is the email module. It lets you build MIME-compliant messages that you then send with smtplib.

Lepore answered 27/12, 2009 at 14:39 Comment(0)
G
4

Well, attachments are not treated in any special ways, they are "just" leaves of the Message-object tree. You can find the answers to any questions regarding MIME-compliant mesasges in this section of the documentation on the email python package.

In general, any kind of attachment (read: raw binary data) can be represented by using base64 (or similar) Content-Transfer-Encoding.

Glottology answered 27/12, 2009 at 14:50 Comment(0)
W
4

Here's how to send e-mails with zip file attachments and utf-8 encoded subject+body.

It was not straightforward to figure this one out, due to lack of documentation and samples for this particular case.

Non-ascii characters in replyto needs to be encoded with, for instance, ISO-8859-1. There probably exists a function that can do this.

Tip:
Send yourself an e-mail, save it and examine the content to figure out how to do the same thing in Python.

Here's the code, for Python 3:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:set ts=4 sw=4 et:

from os.path import basename
from smtplib import SMTP
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.utils import parseaddr, formataddr
from base64 import encodebytes

def send_email(recipients=["[email protected]"],
         subject="Test subject æøå",
         body="Test body æøå",
         zipfiles=[],
         server="smtp.somewhere.xyz",
         username="bob",
         password="password123",
         sender="Bob <[email protected]>",
         replyto="=?ISO-8859-1?Q?M=F8=F8=F8?= <[email protected]>"): #: bool
    """Sends an e-mail"""
    to = ",".join(recipients)
    charset = "utf-8"
    # Testing if body can be encoded with the charset
    try:
        body.encode(charset)
    except UnicodeEncodeError:
        print("Could not encode " + body + " as " + charset + ".")
        return False

    # Split real name (which is optional) and email address parts
    sender_name, sender_addr = parseaddr(sender)
    replyto_name, replyto_addr = parseaddr(replyto)

    sender_name = str(Header(sender_name, charset))
    replyto_name = str(Header(replyto_name, charset))

    # Create the message ('plain' stands for Content-Type: text/plain)
    try:
        msgtext = MIMEText(body.encode(charset), 'plain', charset)
    except TypeError:
        print("MIMEText fail")
        return False

    msg = MIMEMultipart()

    msg['From'] = formataddr((sender_name, sender_addr))
    msg['To'] = to #formataddr((recipient_name, recipient_addr))
    msg['Reply-to'] = formataddr((replyto_name, replyto_addr))
    msg['Subject'] = Header(subject, charset)

    msg.attach(msgtext)

    for zipfile in zipfiles:
        part = MIMEBase('application', "zip")
        b = open(zipfile, "rb").read()
        # Convert from bytes to a base64-encoded ascii string
        bs = encodebytes(b).decode()
        # Add the ascii-string to the payload
        part.set_payload(bs)
        # Tell the e-mail client that we're using base 64
        part.add_header('Content-Transfer-Encoding', 'base64')
        part.add_header('Content-Disposition', 'attachment; filename="%s"' %
                        os.path.basename(zipfile))
        msg.attach(part)

    s = SMTP()
    try:
        s.connect(server)
    except:
        print("Could not connect to smtp server: " + server)
        return False

    if username:
        s.login(username, password)
    print("Sending the e-mail")
    s.sendmail(sender, recipients, msg.as_string())
    s.quit()
    return True

def main():
    send_email()

if __name__ == "__main__":
    main()
Waistcoat answered 29/1, 2010 at 10:17 Comment(0)
D
2
# -*- coding: utf-8 -*-

"""
Mail sender
"""

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

import smtplib
import pystache
import codecs
import time

import sys
reload(sys)
sys.setdefaultencoding('utf-8')


HOST = 'smtp.exmail.qq.com'
PORT = 587
USER = '[email protected]'
PASS = 'yourpass'
FROM = '[email protected]'

SUBJECT = 'subject'
HTML_NAME = 'tpl.html'
CSV_NAME = 'list.txt'
FAILED_LIST = []


def send(mail_receiver, mail_to):
    # text = mail_text
    html = render(mail_receiver)

    # msg = MIMEMultipart('alternative')
    msg = MIMEMultipart('mixed')
    msg['From'] = FROM
    msg['To'] = mail_to.encode()
    msg['Subject'] = SUBJECT.encode()

    # msg.attach(MIMEText(text, 'plain', 'utf-8'))
    msg.attach(MIMEText(html, 'html', 'utf-8'))

    try:
        _sender = smtplib.SMTP(
            HOST,
            PORT
        )
        _sender.starttls()
        _sender.login(USER, PASS)
        _sender.sendmail(FROM, mail_to, msg.as_string())
        _sender.quit()
        print "Success"
    except smtplib.SMTPException, e:
        print e
        FAILED_LIST.append(mail_receiver + ',' + mail_to)


def render(name):
    _tpl = codecs.open(
        './html/' + HTML_NAME,
        'r',
        'utf-8'
    )
    _html_string = _tpl.read()
    return pystache.render(_html_string, {
        'receiver': name
    })


def main():
    ls = open('./csv/' + CSV_NAME, 'r')
    mail_list = ls.read().split('\r')

    for _receiver in mail_list:
        _tmp = _receiver.split(',')
        print 'Mail: ' + _tmp[0] + ',' + _tmp[1]
        time.sleep(20)
        send(_tmp[0], _tmp[1])

    print FAILED_LIST


main()
Dauntless answered 2/11, 2016 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.