348

How to send HTML content in email using Python? I can send simple texts.

My Car
  • 4,198
  • 5
  • 17
  • 50
ha22109
  • 8,036
  • 13
  • 44
  • 48
  • 2
    If you want to send a HTML with unicode see here: http://stackoverflow.com/questions/36397827/send-html-mail-with-unicode – guettli Apr 11 '16 at 10:53
  • 1
    Just a big fat warning. If you are sending non-[ASCII](http://en.wikipedia.org/wiki/ASCII) email using Python < 3.0, consider using the email in [Django](http://en.wikipedia.org/wiki/Django_%28web_framework%29). It wraps [UTF-8](http://en.wikipedia.org/wiki/UTF-8) strings correctly, and also is much simpler to use. You have been warned :-) – Anders Rune Jensen Dec 14 '09 at 14:42

12 Answers12

531

From Python v2.7.14 documentation - 18.1.11. email: Examples:

Here’s an example of how to create an HTML message with an alternative plain text version:

#! /usr/bin/python

import smtplib

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

# me == my email address
# you == recipient's email address
me = "my@email.com"
you = "your@email.com"

# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Link"
msg['From'] = me
msg['To'] = you

# Create the body of the message (a plain-text and an HTML version).
text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.python.org"
html = """\
<html>
  <head></head>
  <body>
    <p>Hi!<br>
       How are you?<br>
       Here is the <a href="http://www.python.org">link</a> you wanted.
    </p>
  </body>
</html>
"""

# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')

# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)

# Send the message via local SMTP server.
s = smtplib.SMTP('localhost')
# sendmail function takes 3 arguments: sender's address, recipient's address
# and message to send - here it is sent as one string.
s.sendmail(me, you, msg.as_string())
s.quit()
joedamarsio
  • 33
  • 1
  • 2
  • 9
Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • 1
    Is it possible to attach a third and a fourth part, both of which are attachments (one ASCII, one binary)? How would one do that? Thanks. – Hamish Grubijan Dec 05 '10 at 20:55
  • 1
    Hi, I noticed that in the end you `quit` the `s` object. What if I want to send multiple messages? Should I quit everytime I send the message or send them all (in a for loop) and then quit once and for all? – xpanta May 09 '12 at 09:58
  • 4
    Make sure to attach html last, as the preferred(showing) part will be the one attached last. `# According to RFC 2046, the last part of a multipart message, in this case # the HTML message, is best and preferred.` I wish i read this 2hrs ago – dwkd Jul 04 '15 at 21:44
  • This doesn't work when we have css classes defined wrapped in Style tag I have created a sample HTML here : http://pastebin.com/QFKLyBJz – Kartik Domadiya Jul 14 '15 at 04:29
  • 1
    Warning: this fails if you have non-ascii characters in the text. – guettli Apr 04 '16 at 08:23
  • Ok its great but what I want to know here is how do a add a variables value to a link for creating a get request link . I mean in the example it should be something like link – TaraGurung Jun 04 '16 at 11:15
  • 8
    Hmm, I get the error for msg.as_string(): list object has no attribute encode – WJA Feb 11 '19 at 14:44
  • why `MIMEText(text, 'plain')` is necessary? – Umair Ayub Feb 17 '19 at 12:11
  • For non-ascii characters in `plain` text you should use this: `part1 = MIMEText(text, _charset="UTF-8")` – Habib Karbasian May 12 '19 at 17:35
  • I cannot have both `plain` text with `html` together. Everytime I attach `html` message, it masks `plain` text. Does anybody know what I am doing wrong? – Habib Karbasian May 12 '19 at 18:06
  • Please check my question how to make the email show the out put of the python script. https://stackoverflow.com/questions/56381324/how-do-i-integrate-a-html-code-in-a-python-script – Piyush Patil May 30 '19 at 20:26
  • It would be nice if StackOverflow could implement NLP for similarity to documentation. Imagine how helpful that would be. – David Warshawsky Aug 04 '20 at 23:52
  • Can we pass both plain text and html body in email? – xyz_scala Jul 19 '23 at 15:09
80

Here is a Gmail implementation of the accepted answer:

import smtplib

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

# me == my email address
# you == recipient's email address
me = "my@email.com"
you = "your@email.com"

# Create message container - the correct MIME type is multipart/alternative.
msg = MIMEMultipart('alternative')
msg['Subject'] = "Link"
msg['From'] = me
msg['To'] = you

# Create the body of the message (a plain-text and an HTML version).
text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.python.org"
html = """\
<html>
  <head></head>
  <body>
    <p>Hi!<br>
       How are you?<br>
       Here is the <a href="http://www.python.org">link</a> you wanted.
    </p>
  </body>
</html>
"""

# Record the MIME types of both parts - text/plain and text/html.
part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')

# Attach parts into message container.
# According to RFC 2046, the last part of a multipart message, in this case
# the HTML message, is best and preferred.
msg.attach(part1)
msg.attach(part2)
# Send the message via local SMTP server.
mail = smtplib.SMTP('smtp.gmail.com', 587)

mail.ehlo()

mail.starttls()

mail.login('userName', 'password')
mail.sendmail(me, you, msg.as_string())
mail.quit()
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
DataTx
  • 1,839
  • 3
  • 26
  • 49
  • 4
    Great code, it works for me, if i turned on [low security in google](https://www.google.com/settings/security/lesssecureapps) – Tovask Jun 25 '15 at 12:49
  • 27
    I use a google [application specific password](https://support.google.com/accounts/answer/185833) with python smtplib, did the trick without having to go low security. – yoyo Jun 30 '15 at 22:48
  • 2
    for anyone reading the above comments: You only require an "App Password" if you have previously enabled 2 step verification in your Gmail account. – Mugen Oct 11 '18 at 16:21
  • Is there a way to append something dynamically in the HTML part of the message? – magma May 09 '20 at 13:59
  • Somehow only the last attached part seems to work. – Zobayer Hasan Jun 28 '21 at 20:28
71

You might try using my mailer module.

from mailer import Mailer
from mailer import Message

message = Message(From="me@example.com",
                  To="you@example.com")
message.Subject = "An HTML Email"
message.Html = """<p>Hi!<br>
   How are you?<br>
   Here is the <a href="http://www.python.org">link</a> you wanted.</p>"""

sender = Mailer('smtp.example.com')
sender.send(message)
Honest Abe
  • 8,430
  • 4
  • 49
  • 64
Ryan Ginstrom
  • 13,915
  • 5
  • 45
  • 60
  • Mailer module is great however it claims to work with Gmail, but doesn't and there are no docs. – MFB Jul 18 '12 at 19:24
  • 1
    @MFB -- Have you tried the Bitbucket repo? https://bitbucket.org/ginstrom/mailer/ – Ryan Ginstrom Aug 16 '12 at 04:11
  • 4
    For gmail one should provide `use_tls=True`,`usr='email'` and `pwd='password'` when initializing `Mailer` and it will work. – ToonAlfrink Jul 21 '14 at 08:21
  • I would recommend adding to your code the following line right after the message.Html line: `message.Body = """Some text to show when the client cannot show HTML emails"""` – IvanD Aug 05 '15 at 03:37
  • great but how to add the variables values to the link i mean creating a link like this link So that i can access that values from the routes it goes to . Thanks – TaraGurung Jun 04 '16 at 11:17
64

Here is a simple way to send an HTML email, just by specifying the Content-Type header as 'text/html':

import email.message
import smtplib

msg = email.message.Message()
msg['Subject'] = 'foo'
msg['From'] = 'sender@test.com'
msg['To'] = 'recipient@test.com'
msg.add_header('Content-Type','text/html')
msg.set_payload('Body of <b>message</b>')

# Send the message via local SMTP server.
s = smtplib.SMTP('localhost')
s.starttls()
s.login(email_login,
        email_passwd)
s.sendmail(msg['From'], [msg['To']], msg.as_string())
s.quit()
taltman
  • 198
  • 2
  • 6
  • 2
    This is a nice simple answer, handy for quick and dirty scripts, thanks. BTW one can refer to the accepted answer for a simple `smtplib.SMTP()` example, which doesn't use tls. I used this for an internal script at work where we use ssmtp and a local mailhub. Also, this example is missing `s.quit()`. – Mike S Sep 02 '15 at 14:42
  • 1
    "mailmerge_conf.smtp_server" is not defined... at least is what Python 3.6 says... – ZEE Dec 27 '17 at 20:47
  • Absolutely beautiful! Adding .add_header and using .set_payload instead of .set_content worked like a charm for me! Emails looking snazzy now. Thanks again! – MegaBytten Jul 27 '22 at 08:56
53

for python3, improve @taltman 's answer:

  • use email.message.EmailMessage instead of email.message.Message to construct email.
  • use email.set_content func, assign subtype='html' argument. instead of low level func set_payload and add header manually.
  • use SMTP.send_message func instead of SMTP.sendmail func to send email.
  • use with block to auto close connection.
from email.message import EmailMessage
from smtplib import SMTP

# construct email
email = EmailMessage()
email['Subject'] = 'foo'
email['From'] = 'sender@test.com'
email['To'] = 'recipient@test.com'
email.set_content('<font color="red">red color text</font>', subtype='html')

# Send the message via local SMTP server.
with smtplib.SMTP('localhost') as s:
    s.login('foo_user', 'bar_password')
    s.send_message(email)
valleygtc
  • 99
  • 4
  • 4
  • 3
    As an improvement to this if you want to send an attachment in addition, use `email.add_alternative()` (in the same way as you have with `email.set_content()` to add HTML, and then add an attachment using `email.add_attachment()` (This took me bloomin ages to figure out) – Zilbert97 Oct 30 '20 at 20:26
  • 2
    The `EmailMessage` API was only officially available starting in Python 3.6, though it was shipped as an alternative already in 3.3. New code should absolutely use this instead of the old `email.message.Message` legacy API which most of the answers here unfortunately still suggest. Anything with `MIMEText` or `MIMEMultipart` is the old API and should be avoided unless you have legacy reasons. – tripleee Feb 08 '22 at 20:53
  • is there a way to use a file instead of typing out the HTML in `email.set context`? – Michael Aug 18 '22 at 10:05
  • I found the way to add a HTML files. ``` report_file = open('resetpasswordemail.html') html = report_file.read() em.set_content(html, subtype='html')``` – Michael Aug 18 '22 at 10:28
11

Here's sample code. This is inspired from code found on the Python Cookbook site (can't find the exact link)

def createhtmlmail (html, text, subject, fromEmail):
    """Create a mime-message that will render HTML in popular
    MUAs, text in better ones"""
    import MimeWriter
    import mimetools
    import cStringIO

    out = cStringIO.StringIO() # output buffer for our message 
    htmlin = cStringIO.StringIO(html)
    txtin = cStringIO.StringIO(text)

    writer = MimeWriter.MimeWriter(out)
    #
    # set up some basic headers... we put subject here
    # because smtplib.sendmail expects it to be in the
    # message body
    #
    writer.addheader("From", fromEmail)
    writer.addheader("Subject", subject)
    writer.addheader("MIME-Version", "1.0")
    #
    # start the multipart section of the message
    # multipart/alternative seems to work better
    # on some MUAs than multipart/mixed
    #
    writer.startmultipartbody("alternative")
    writer.flushheaders()
    #
    # the plain text section
    #
    subpart = writer.nextpart()
    subpart.addheader("Content-Transfer-Encoding", "quoted-printable")
    pout = subpart.startbody("text/plain", [("charset", 'us-ascii')])
    mimetools.encode(txtin, pout, 'quoted-printable')
    txtin.close()
    #
    # start the html subpart of the message
    #
    subpart = writer.nextpart()
    subpart.addheader("Content-Transfer-Encoding", "quoted-printable")
    #
    # returns us a file-ish object we can write to
    #
    pout = subpart.startbody("text/html", [("charset", 'us-ascii')])
    mimetools.encode(htmlin, pout, 'quoted-printable')
    htmlin.close()
    #
    # Now that we're done, close our writer and
    # return the message body
    #
    writer.lastpart()
    msg = out.getvalue()
    out.close()
    print msg
    return msg

if __name__=="__main__":
    import smtplib
    html = 'html version'
    text = 'TEST VERSION'
    subject = "BACKUP REPORT"
    message = createhtmlmail(html, text, subject, 'From Host <sender@host.com>')
    server = smtplib.SMTP("smtp_server_address","smtp_port")
    server.login('username', 'password')
    server.sendmail('sender@host.com', 'target@otherhost.com', message)
    server.quit()
Nathan
  • 2,955
  • 1
  • 19
  • 17
  • FYI; this came from http://code.activestate.com/recipes/67083-send-html-mail-from-python/history/1/ – Dale E. Moore Jul 28 '14 at 23:19
  • 1
    This is even older and cruftier than the `email.message.Message` API. The `MimeWriter` library was deprecated in Python 2.3 in 2003. – tripleee Feb 08 '22 at 20:58
6

Actually, yagmail took a bit different approach.

It will by default send HTML, with automatic fallback for incapable email-readers. It is not the 17th century anymore.

Of course, it can be overridden, but here goes:

import yagmail
yag = yagmail.SMTP("me@example.com", "mypassword")

html_msg = """<p>Hi!<br>
              How are you?<br>
              Here is the <a href="http://www.python.org">link</a> you wanted.</p>"""

yag.send("to@example.com", "the subject", html_msg)

For installation instructions and many more great features, have a look at the github.

PascalVKooten
  • 20,643
  • 17
  • 103
  • 160
4

Here's a working example to send plain text and HTML emails from Python using smtplib along with the CC and BCC options.

https://varunver.wordpress.com/2017/01/26/python-smtplib-send-plaintext-and-html-emails/

#!/usr/bin/env python
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def send_mail(params, type_):
      email_subject = params['email_subject']
      email_from = "from_email@domain.com"
      email_to = params['email_to']
      email_cc = params.get('email_cc')
      email_bcc = params.get('email_bcc')
      email_body = params['email_body']

      msg = MIMEMultipart('alternative')
      msg['To'] = email_to
      msg['CC'] = email_cc
      msg['Subject'] = email_subject
      mt_html = MIMEText(email_body, type_)
      msg.attach(mt_html)

      server = smtplib.SMTP('YOUR_MAIL_SERVER.DOMAIN.COM')
      server.set_debuglevel(1)
      toaddrs = [email_to] + [email_cc] + [email_bcc]
      server.sendmail(email_from, toaddrs, msg.as_string())
      server.quit()

# Calling the mailer functions
params = {
    'email_to': 'to_email@domain.com',
    'email_cc': 'cc_email@domain.com',
    'email_bcc': 'bcc_email@domain.com',
    'email_subject': 'Test message from python library',
    'email_body': '<h1>Hello World</h1>'
}
for t in ['plain', 'html']:
    send_mail(params, t)
fenceop
  • 1,439
  • 3
  • 18
  • 29
Varun Verma
  • 704
  • 2
  • 10
  • 20
2

I may be late in providing an answer here, but the Question asked a way to send HTML emails. Using a dedicated module like "email" is okay, but we can achieve the same results without using any new module. It all boils down to the Gmail Protocol.

Below is my simple sample code for sending HTML mail only by using "smtplib" and nothing else.

```
import smtplib

FROM = "....@gmail.com"
TO = "another....@gmail.com"
SUBJECT= "Subject"
PWD = "thesecretkey"

TEXT="""
<h1>Hello</h1>
""" #Your Message (Even Supports HTML Directly)

message = f"Subject: {SUBJECT}\nFrom: {FROM}\nTo: {TO}\nContent-Type: text/html\n\n{TEXT}" #This is where the stuff happens

try:
    server=smtplib.SMTP("smtp.gmail.com",587)
    server.ehlo()
    server.starttls()
    server.login(FROM,PWD)
    server.sendmail(FROM,TO,message)
    server.close()
    print("Successfully sent the mail.")
except Exception as e:
    print("Failed to send the mail..", e)
```
Vaibhav VK
  • 21
  • 2
  • The `email` library is not a "new module", it is part of the standard library just like `smtplib`. You should absolutely not create messages by combining strings like this; it will seem to work for very simple messages, but fail in catastrophic ways as soon as you move outside the comfortable realm of a single `text/plain` payload with only 7-bit US-ASCII contents (and even then there are corner cases which are likely to trip you up, especially if you don't know exactly what you are doing). – tripleee Feb 08 '22 at 10:14
1

Here is my answer for AWS using boto3

    subject = "Hello"
    html = "<b>Hello Consumer</b>"

    client = boto3.client('ses', region_name='us-east-1', aws_access_key_id="your_key",
                      aws_secret_access_key="your_secret")

client.send_email(
    Source='ACME <do-not-reply@acme.com>',
    Destination={'ToAddresses': [email]},
    Message={
        'Subject': {'Data': subject},
        'Body': {
            'Html': {'Data': html}
        }
    }
Trent
  • 2,122
  • 1
  • 24
  • 38
1

Simplest solution for sending email from Organizational account in Office 365:

from O365 import Message

html_template =     """ 
            <html>
            <head>
                <title></title>
            </head>
            <body>
                    {}
            </body>
            </html>
        """

final_html_data = html_template.format(df.to_html(index=False))

o365_auth = ('sender_username@company_email.com','Password')
m = Message(auth=o365_auth)
m.setRecipients('receiver_username@company_email.com')
m.setSubject('Weekly report')
m.setBodyHTML(final_html_data)
m.sendMessage()

here df is a dataframe converted to html Table, which is being injected to html_template

tejasvi88
  • 635
  • 7
  • 15
Gil Baggio
  • 13,019
  • 3
  • 48
  • 37
  • 2
    The question doesn't mention anything about using Office or an organizational account. Good contribution but not very helpful to the asker – Mwikala Kangwa Mar 25 '20 at 14:49
1

In case you want something simpler:

from redmail import EmailSender
email = EmailSender(host="smtp.myhost.com", port=1)

email.send(
    subject="Example email",
    sender="me@example.com",
    receivers=["you@example.com"],
    html="<h1>Hi, this is HTML body</h1>"
)

Pip install Red Mail from PyPI:

pip install redmail

Red Mail has most likely all you need from sending emails and it has a lot of features including:

Documentation: https://red-mail.readthedocs.io/en/latest/index.html

Source code: https://github.com/Miksus/red-mail

miksus
  • 2,426
  • 1
  • 18
  • 34