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
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.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.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.
"""
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 = "
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)

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 = Htmlp.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:Sweet!
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__())
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
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 MIMETextwell 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.
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.
@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.
Having CC and BCC would be useful.
@Obi-sama
Good suggestion, thanks. I’ll include that in the next version of the module.
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.
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.
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.
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.
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.
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?
Including CC and BBC code would be nice.
@Tim
The latest source at Bitbucket supports both CC and BCC.
[...] 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 [...]
Thanks for this man, saved my day.
Hey, pretty cool module. Here is simple wrapper for terminal use: http://kitakitsune.org/textydw/tools/mailer.
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
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?
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.