A module to send email simply in Python

Update: I've released version 0.3 of the mailer module. See the mailer home page for details and the latest version.

The email and smtplib modules in Python are very powerful, but they're also a bit complex when you just want to send an email.

I wrote the mailer module as a front end to these two modules, in order to make the task of sending email in Python using SMTP as simple as possible.

Below are some examples.

Send a simple plain text email

import mailer

message = mailer.Message()
message.From = "me@example.com"
message.To = "you@example.com"
message.Subject = "My Vacation"
message.Body = open("letter.txt", "rb").read()

mailer = mailer.Mailer('mail.example.com')
mailer.send(message)

Send an email with an attachment

message = mailer.Message()
message.From = "me@example.com"
message.To = "you@example.com"
message.Subject = "My Vacation"
message.Body = open("letter.txt", "rb").read()
message.attach("picture.jpg")

mailer = mailer.Mailer('mail.example.com')
mailer.send(message)

Send an HTML email

message = mailer.Message()
message.From = "me@example.com"
message.To = "you@example.com"
message.Subject = "My Vacation"
message.Body = open("letter.txt", "rb").read()
message.Html = """This email is in <b>HTML</b>.
<a href="
http://example.com">Here's a link.</a>"""

mailer = mailer.Mailer('mail.example.com')
mailer.send(message)

Download the mailer module (zip file).

Edit: Here's the source code of the mailer module.

#coding: UTF8
"""
mailer module

Simple front end to the smtplib and email modules,
to simplify sending email.

A lot of this code was taken from the online examples in the
email module documentation:
http://docs.python.org/library/email-examples.html

Released under MIT license.

Sample code:

import mailer

message = mailer.Message()
message.From = "me@example.com"
message.To = "
you@example.com"
message.Subject = "
My Vacation"
message.Body = open("
letter.txt", "rb").read()
message.attach("
picture.jpg")

mailer = mailer.Mailer('mail.example.com')
mailer.send(message)

"""
import smtplib

# Import the email modules we'll need
from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# For guessing MIME type based on file name extension
import mimetypes

from os import path

__version__ = "0.1"
__author__ = "Ryan Ginstrom"
__license__ = "MIT"

class Mailer(object):
    """
    Represents an SMTP connection.

    Use login() to log in with a username and password.
    """

    def __init__(self, host="localhost"):
        self.host = host
        self._usr = None
        self._pwd = None

    def login(self, usr, pwd):
        self._usr = usr
        self._pwd = pwd

    def send(self, msg):
        """
        Send one message or a sequence of messages.

        Every time you call send, the mailer creates a new
        connection, so if you have several emails to send, pass
        them as a list:
        mailer.send([msg1, msg2, msg3])
        """
        server = smtplib.SMTP(self.host)

        if self._usr and self._pwd:
            server.login(self._usr, self._pwd)

        try:
            for m in msg:
                self._send(server, m)
        except TypeError:
            self._send(server, msg)

        server.quit()

    def _send(self, server, msg):
        """
        Sends a single message using the server
        we created in send()
        "
""
        me = msg.From
        you = [x.strip() for x in msg.To.split(",")]
        server.sendmail(me, you, msg.as_string())

class Message(object):
    """
    Represents an email message.

    Set the To, From, Subject, and Body attributes as plain-text strings.
    Optionally, set the Html attribute to send an HTML email, or use the
    attach() method to attach files.

    Even when sending an HTML email, you have to set the Body
    attribute as the alternative text version.

    Send using the Mailer class.
    """

    def __init__(self):
        self.attachments = []
        self._to = None
        self.From = None
        self.Subject = None
        self.Body = None
        self.Html = None

    def _get_to(self):
        addrs = self._to.replace(";", ",").split(",")
        return ", ".join([x.strip()
                          for x in addrs])
    def _set_to(self, to):
        self._to = to

    To = property(_get_to, _set_to,
                  doc="""The recipient(s) of the email.
                  Separate multiple recipients with commas or semicolons"
"")

    def as_string(self):
        """Get the email as a string to send in the mailer"""

        if not self.attachments:
            return self._plaintext()
        else:
            return self._multipart()

    def _plaintext(self):
        """Plain text email with no attachments"""

        if not self.Html:
            msg = MIMEText(self.Body)
        else:
            msg  = self._with_html()

        self._set_info(msg)
        return msg.as_string()

    def _with_html(self):
        """There's an html part"""

        outer = MIMEMultipart('alternative')

        part1 = MIMEText(self.Body, 'plain')
        part2 = MIMEText(self.Html, 'html')

        outer.attach(part1)
        outer.attach(part2)

        return outer

    def _set_info(self, msg):
        msg['Subject'] = self.Subject
        msg['From'] = self.From
        msg['To'] = self.To

    def _multipart(self):
        """The email has attachments"""

        msg = MIMEMultipart()

        msg.attach(MIMEText(self.Body, 'plain'))

        self._set_info(msg)
        msg.preamble = self.Subject

        for filename in self.attachments:
            self._add_attachment(msg, filename)
        return msg.as_string()

    def _add_attachment(self, outer, filename):
        ctype, encoding = mimetypes.guess_type(filename)
        if ctype is None or encoding is not None:
            # No guess could be made, or the file is encoded (compressed), so
            # use a generic bag-of-bits type.
            ctype = 'application/octet-stream'
        maintype, subtype = ctype.split('/', 1)
        fp = open(filename, 'rb')
        if maintype == 'text':
            # Note: we should handle calculating the charset
            msg = MIMEText(fp.read(), _subtype=subtype)
        elif maintype == 'image':
            msg = MIMEImage(fp.read(), _subtype=subtype)
        elif maintype == 'audio':
            msg = MIMEAudio(fp.read(), _subtype=subtype)
        else:
            msg = MIMEBase(maintype, subtype)
            msg.set_payload(fp.read())
            # Encode the payload using Base64
            encoders.encode_base64(msg)
        fp.close()
        # Set the filename parameter
        msg.add_header('Content-Disposition',
                'attachment',
                filename=path.basename(filename))
        outer.attach(msg)

    def attach(self, filename):
        """
        Attach a file to the email. Specify the name of the file;
        Message will figure out the MIME type and load the file.
        "
""

        self.attachments.append(filename)

45 comments to A module to send email simply in Python

  • D

    Would changing the message class as follows break anything for you? I think it makes the class a bit more flexible. I’m also curious why you made To a property like you did. It’s saved as a string, but when you get it back it standardizes the separator to commas, and then you split it again in the mailer before mailing it. What made you choose to do it like that?

    def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None, attachments=[]):
    
            self.attachments = attachments
    
            self._to = To
    
            self.From = From
    
            self.Subject = Subject
    
            self.Body = Body
    
            self.Html = Html
  • D

    p.s. your “pre” tag doesn’t work!

  • @D

    Adding the parameters to the __init__() class is a good idea.

    I made “To” a property so that users could separate emails by either semicolons or commas, and it would automatically change it to a comma-separated list.

  • @D

    Not sure what went wrong with the “pre” tags — I tried editing your comment and it seems to have worked.

    By the way, using “[]” in the arguments list is dangerous. Probably better to say attachments=None, and then do:

    self.attachments = attachments or []
  • Bill

    Thanks for your script. I sent an HTML message and it worked fine. However your docstring for message.HTML says “Even when sending an HTML email, you have to set the Body attribute as the alternative text version.” But I omitted the Body attribute and my message was sent anyway. Could you explain further why an alternative plaintext message has to accompany the html version when its omission does not cause an error?

  • @Bill

    It’s because of this line:
    part1 = MIMEText(self.Body, ‘plain’)

    Body is None by default. I thought that would be bad, but actually, MIMEText handles None like a champ and just creates an empty text part. Thanks for spotting this — something I need to fix for version 0.2 (along with ability to set values in the Message class via __init__())

  • C Saha

    Hi

    This is a great script but its not working on my unix server where I have Python 2.4.4, however this is working on my desktop when I tested as I have Python 2.5.1.

    Can you help me to get it executed in Python 2.4.4?

    Thanks
    C S

  • @C Saha

    What kind of errors did you get?

  • thanks for the module. and here is a patch to make it run even under py 2.4

    change

    from email.message import Message
    from email.mime.audio import MIMEAudio
    from email.mime.base import MIMEBase
    from email.mime.image import MIMEImage
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    

    to

    try:
        from email.message import Message
        from email.mime.audio import MIMEAudio
        from email.mime.base import MIMEBase
        from email.mime.image import MIMEImage
        from email.mime.multipart import MIMEMultipart
        from email.mime.text import MIMEText
    except:
        from email.MIMEMessage import Message
        from email.MIMEAudio import MIMEAudio
        from email.MIMEBase import MIMEBase
        from email.MIMEImage import MIMEImage
        from email.MIMEMultipart import MIMEMultipart
        from email.MIMEText import MIMEText
    
  • well I hope my post without format will not let things confused. And I hope the new version can apply this patch. C U

  • @oyster

    Thanks for the code. Yes, I’ll incorporate this into version 0.2. :)

    I also fixed your comment’s formatting by replacing [code] with <pre>

  • I have one problem: what if a charset must be given for the body? for example, I am using Chinese, this module can send mail which have Chinese title well. But the body is shown in a mess for some mail server, but is ok for gmail. How to point out the encode/decode of the email body? thanx

  • I get this in a rush, I don’t know whether there is a bug

    the only difference – for user- between the old version lies in the construction of the Message if you want to use your owen charset (the default is us-ascii)

    #coding=gb2312
    ...
    
    message=mailer.Message('gb2312')  #this is for Chinese
    
    #message=mailer.Message()  #you can use this too, if simple ascii works for your language
    
    
    ...
    
    
    
    this is the src
    
    #coding: UTF8
    """
    mailer module
    
    Simple front end to the smtplib and email modules,
    to simplify sending email.
    
    A lot of this code was taken from the online examples in the
    email module documentation:
    http://docs.python.org/library/email-examples.html
    
    Released under MIT license.
    
    Sample code:
    
    import mailer
    
    message = mailer.Message()
    message.From = "me@example.com"
    message.To = "you@example.com"
    message.Subject = "My Vacation"
    message.Body = open("letter.txt", "rb").read()
    message.attach("picture.jpg")
    
    mailer = mailer.Mailer('mail.example.com')
    mailer.send(message)
    
    """
    import smtplib
    
    # Import the email modules we'll need
    from email import encoders
    
    try:
        from email.message import Message
        from email.mime.audio import MIMEAudio
        from email.mime.base import MIMEBase
        from email.mime.image import MIMEImage
        from email.mime.multipart import MIMEMultipart
        from email.mime.text import MIMEText
    except:
        from email.MIMEMessage import Message
        from email.MIMEAudio import MIMEAudio
        from email.MIMEBase import MIMEBase
        from email.MIMEImage import MIMEImage
        from email.MIMEMultipart import MIMEMultipart
        from email.MIMEText import MIMEText
    
    # For guessing MIME type based on file name extension
    import mimetypes
    
    from os import path
    
    __version__ = "0.1"
    __author__ = "Ryan Ginstrom"
    __license__ = "MIT"
    
    class Mailer(object):
        """
        Represents an SMTP connection.
    
        Use login() to log in with a username and password.
        """
    
        def __init__(self, host="localhost"):
            self.host = host
            self._usr = None
            self._pwd = None
    
        def login(self, usr, pwd):
            self._usr = usr
            self._pwd = pwd
    
        def send(self, msg):
            """
            Send one message or a sequence of messages.
    
            Every time you call send, the mailer creates a new
            connection, so if you have several emails to send, pass
            them as a list:
            mailer.send([msg1, msg2, msg3])
            """
            server = smtplib.SMTP(self.host)
    
            if self._usr and self._pwd:
                server.login(self._usr, self._pwd)
    
            try:
                for m in msg:
                    self._send(server, m)
            except TypeError:
                self._send(server, msg)
    
            server.quit()
    
        def _send(self, server, msg):
            """
            Sends a single message using the server
            we created in send()
            """
            me = msg.From
            you = [x.split() for x in msg.To.split(",")]
            server.sendmail(me, you, msg.as_string())
    
    class Message(object):
        """
        Represents an email message.
    
        Set the To, From, Subject, and Body attributes as plain-text strings.
        Optionally, set the Html attribute to send an HTML email, or use the
        attach() method to attach files.
    
        Even when sending an HTML email, you have to set the Body attribute as
        the alternative text version.
    
        Send using the Mailer class.
        """
    
        def __init__(self, charset=None):
            self.attachments = []
            self._to = None
            self.From = None
            self.Subject = None
            self.Body = None
            self.Html = None
    
            self.charset=charset or 'us-ascii'
            print self.charset
    
        def _get_to(self):
            addrs = self._to.replace(";", ",").split(",")
            return ", ".join([x.strip()
                              for x in addrs])
        def _set_to(self, to):
            self._to = to
    
        To = property(_get_to, _set_to,
                      doc="""The recipient(s) of the email.
                      Separate multiple recipients with commas or semicolons""")
    
        def as_string(self):
            """Get the email as a string to send in the mailer"""
    
            if not self.attachments:
                return self._plaintext()
            else:
                return self._multipart()
    
        def _plaintext(self):
            """Plain text email with no attachments"""
    
            if not self.Html:
                msg = MIMEText(self.Body, _subtype='html', _charset=self.charset)
            else:
                msg  = self._with_html()
    
            self._set_info(msg)
            return msg.as_string()
    
        def _with_html(self):
            """There's an html part"""
    
            outer = MIMEMultipart('alternative')
    
            part1 = MIMEText(self.Body, 'plain')
            part2 = MIMEText(self.Html, 'html')
    
            outer.attach(part1)
            outer.attach(part2)
    
            return outer
    
        def _set_info(self, msg):
            msg['Subject'] = self.Subject
            msg['From'] = self.From
            msg['To'] = self.To
    
        def _multipart(self):
            """The email has attachments"""
    
            msg = MIMEMultipart()
    
            msg.attach(MIMEText(self.Body, 'plain', self.charset))
    
            self._set_info(msg)
            msg.preamble = self.Subject
    
            for filename in self.attachments:
                self._add_attachment(msg, filename)
            return msg.as_string()
    
        def _add_attachment(self, outer, filename):
            ctype, encoding = mimetypes.guess_type(filename)
            if ctype is None or encoding is not None:
                # No guess could be made, or the file is encoded (compressed), so
                # use a generic bag-of-bits type.
                ctype = 'application/octet-stream'
            maintype, subtype = ctype.split('/', 1)
            fp = open(filename, 'rb')
            if maintype == 'text':
                # Note: we should handle calculating the charset
                msg = MIMEText(fp.read(), _subtype=subtype, _charset=self.charset)
            elif maintype == 'image':
                msg = MIMEImage(fp.read(), _subtype=subtype)
            elif maintype == 'audio':
                msg = MIMEAudio(fp.read(), _subtype=subtype)
            else:
                msg = MIMEBase(maintype, subtype)
                msg.set_payload(fp.read())
                # Encode the payload using Base64
                encoders.encode_base64(msg)
            fp.close()
            # Set the filename parameter
            msg.add_header('Content-Disposition', 'attachment', filename=path.basename(filename))
            outer.attach(msg)
    
        def attach(self, filename):
            """
            Attach a file to the email. Specify the name of the file;
            Message will figure out the MIME type and load the file.
            """
    
            self.attachments.append(filename)
  • I get the following error:

    Traceback (most recent call last):
    File “”, line 1, in ?
    ImportError: cannot import name encoders

    Failed on both 2.3 and 2.6. Am I missing something?

  • @amit

    I’m not sure why it would fail to import encoders in python 2.6, but the latest version of the mailer module supports python 2.3. See here for details.

    Also, make sure that you download the actual code instead of copying and pasting. I think that WordPress is probably turning quotes into curly quotes and such.

  • Justin

    I am kinda confused.. I have one mailserver that when i set the mailer.Mailer(‘server1.com’)

    it will send the email to anyone on that domain. but will not let me send it to anyone else (other domain)

    in test 2 sending mail through “server2″ I get an auth problem.

    I am wondering if I am just not doing something correct? or am I missing something?

    Output of server1 when sending to outside domain
    Traceback (most recent call last):
    File “mailtest.py”, line 18, in
    mailer.send(message)
    File “mailer.py”, line 83, in send
    self._send(server, msg)
    File “mailer.py”, line 94, in _send
    server.sendmail(me, you, msg.as_string())
    File “/usr/lib/python2.5/smtplib.py”, line 699, in sendmail
    senderrs[each]=(code,resp)
    TypeError: list objects are unhashable

    Output of server2 when sending through server2
    Traceback (most recent call last):
    File “mailtest.py”, line 18, in
    mailer.send(message)
    File “mailer.py”, line 83, in send
    self._send(server, msg)
    File “mailer.py”, line 94, in _send
    server.sendmail(me, you, msg.as_string())
    File “/usr/lib/python2.5/smtplib.py”, line 692, in sendmail
    raise SMTPSenderRefused(code, resp, from_addr)
    smtplib.SMTPSenderRefused: (550, ’5.7.0 Need to authenticate via SMTP-AUTH-Login {mp-us002}’, ‘blindlove@gmx.com’)

    I am really confused .. is server 1 not secure? is server 2 just requiring some additonal setup?

  • @Justin:

    Maybe you need to log in to send outside your domain?

    As a guess, how about you try this:

    mailer.login("username", "password")
    mailer.send(message)
    
  • I just updated mailer to version 0.3. Please see the mailer home page for the latest version, documentation, and code samples.

  • Shubham Vidyarthi

    @justin and @oyster

    def _send(self, server, msg):
    “””
    Sends a single message using the server
    we created in send()
    “””
    me = msg.From
    you = [x.split() for x in msg.To.split(",")]
    server.sendmail(me, you, msg.as_string())

    it should be x.strip instead of x.split

  • @Shubham Vidyarthi

    Thanks for the catch.

    This is fixed in the latest version of the mailer module.

  • Obi-sama

    Having CC and BCC would be useful.

  • @Obi-sama

    Good suggestion, thanks. I’ll include that in the next version of the module.

  • kenny

    thanks ryan for this, i was having a bit of a headache working with smtplib… mailer solved my attachment issue. :)

  • Hey can this be implemented in Windows environment with Outlook/Exchange technology? – please tell how thanks, Kris…

  • @krisidian

    You can automate Outlook to send emails with attachments, etc. This is quite doable using VBA.

    The problem with Python is that when you automate Outlook from a scripting language (outside its own VBA environment), it will pop up a warning to the user.

    Two ways to get around this: Write a python COM module that is called from a VBA macro within Outlook; or write a C++ “shim” that wraps calls to Outlook, and code against that from Python.

  • Pierre Morel

    Hi,
    This mailer is extremely useful …
    How can I send an HTML email WITH a file attachment?
    Thanks in davnce for your help.
    Pierre

  • @Pierre

    Please download the latest version from pypi. It will let you send an attachment with an HTML email.

  • Inte

    Great module!
    On October 7th, 2009 at 11:37 am you replied to Obi-sama that you’d include support for CC and BCC in the next version.
    When might that be?

  • @ Inte

    I’ve been a bit behind on incorporating the BCC support, as well as a patch that a user sent me to send email via gmail. What I ought to do is put the code on bitbucket or something similar.

  • thetruthbot

    hello ryan, i followed the steps to get the module up and running and still running into problems. I keep gettin an AttributeError: ‘module’ object has no attribute ‘Message’. What does that mean? I am a noob at this.

  • Massimiliano Torromeo

    Hi,
    I tried today to use your mailer class and it works fine, except for the Date field that is missing in the delivered emails and as far as I can see there is not property to set that allows me to explicitly define it.

  • I’ve created a public repository for mailer on bitbucket. I’m planning to incorporate the patches I’ve received shortly.

    If you’d like write access to the repo, please let me know your bitbucket account name, and I’ll add you.

  • thor

    how about error handling?
    how do I do one thing if it sent successfull and do something else if it fails, and my plan is to create a logfile, how do I apped the smtp errors in there?

  • Tim

    Including CC and BBC code would be nice.

  • [...] de lo contrario, puede que el servidor al que enviemos el correo electrónico no lo acepte. En The Git’s Blog encontré una bonita clase que abstrae aun mas la manera de enviar correo, éste es el método que [...]

  • Enn

    Thanks for this man, saved my day.

  • Hey, pretty cool module. Here is simple wrapper for terminal use: http://kitakitsune.org/textydw/tools/mailer.

  • Andy

    Awesome module, makes life so much easier when emailing with Python. Thanks a lot !

  • Very usefully module. Simpy and easy to modify.

  • The mailer module don’t handle internationalization and unicode strings.

    The pyzmail library include support for all
    internationalization, attachment, image embedding, SSL, TLS, authentication, but also know how to parse emails.

    pyzmail also include a lot of samples and documentations

    http://www.magiksys.net/pyzmail

    Enjoy

  • Peter

    When I try to send an email, I get this error:

    Traceback (most recent call last):
    File “C:\Users\Peter\Documents\Python Hello World\Python Hello World.py”, line 10, in
    mailer.send(message)
    File “C:\Python25\mailer.py”, line 74, in send
    server = smtplib.SMTP(self.host)
    File “C:\Python25\lib\smtplib.py”, line 244, in __init__
    (code, msg) = self.connect(host, port)
    File “C:\Python25\lib\smtplib.py”, line 296, in connect
    for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
    gaierror: (11004, ‘getaddrinfo failed’)

    May I please have some help?

  • Peter

    And yes, here is the code:

    import mailer

    message = mailer.Message()
    message.From = “hidden@email.com”
    message.To = “hidden@email.com”
    message.Subject = “Hi”
    message.Body = open(“readme.txt”, “rb”).read()

    mailer = mailer.Mailer(‘mail.gmail.com’)
    mailer.send(message)

    The actual e-mail addresses are not show for privacy.

  • mahendra

    Hi Rayan,

    Thanks for the modules, it really worked like a charm for me, its perfect !
    But I was wondering is there any chance adding BCC and CC with this module ?

    Cheers !

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>