Creating email templates with Django
Asked Answered
K

12

257

I want to send HTML-emails, using Django templates like this:

<html>
<body>
hello <strong>{{username}}</strong>
your account activated.
<img src="mysite.com/logo.gif" />
</body>

I can't find anything about send_mail, and django-mailer only sends HTML templates, without dynamic data.

How do I use Django's template engine to generate e-mails?

Kendry answered 11/5, 2010 at 9:41 Comment(2)
Notice Django 1.7 offers html_message in send_email https://mcmap.net/q/109381/-creating-email-templates-with-djangoRunaway
Hi @anakin, I have struggled with this problem for a long time and decided to create a package for that. I would be very happy to get your feedback : github.com/charlesthk/django-simple-mailMaggoty
S
440

From the docs, to send HTML e-mail you want to use alternative content-types, like this:

from django.core.mail import EmailMultiAlternatives

subject, from_email, to = 'hello', '[email protected]', '[email protected]'
text_content = 'This is an important message.'
html_content = '<p>This is an <strong>important</strong> message.</p>'
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()

You'll probably want two templates for your e-mail - a plain text one that looks something like this, stored in your templates directory under email.txt:

Hello {{ username }} - your account is activated.

and an HTMLy one, stored under email.html:

Hello <strong>{{ username }}</strong> - your account is activated.

You can then send an e-mail using both those templates by making use of get_template, like this:

from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template
from django.template import Context

plaintext = get_template('email.txt')
htmly     = get_template('email.html')

d = Context({ 'username': username })

subject, from_email, to = 'hello', '[email protected]', '[email protected]'
text_content = plaintext.render(d)
html_content = htmly.render(d)
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()
Stallworth answered 11/5, 2010 at 11:30 Comment(8)
I think you can simplify this with render_to_string, which would let you lose the separate lines assigning templates to plaintext and htmly, and just set templates and contexts when you define text_content and html_content.Lollar
@Lollar Can you elaborate what you want to say and how we can use thatCounterirritant
@Counterirritant see andi's answer below, which also simplifies the alternative part thanks to html_message param being added to send_email() in Django 1.7Squier
Pardon me but why do we use txt and htmly both at the same time for a mail. I didnt get this logicAnisotropic
they are just examples to show different kind of methods, you can use any of the them @ShashankVivekWhiteman
Some old mail clients do not support HTML mail, so they will show the plain text version. See docs.djangoproject.com/en/2.0/topics/email/…Thames
Thanks for the helpful answer. Nowadays context should only be a dict. So instead of d = Context({...}), d = {...} is the correct way ;)Northumbria
@Northumbria answer applies for 2021's Django 3.1.5 with python 3.8.5. No need to from django.template import Context. Just do d = { 'username': username }.Polyploid
R
312

Since Django's 1.7 in send_email method the html_message parameter was added.

html_message: If html_message is provided, the resulting email will be a multipart/alternative email with message as the text/plain content type and html_message as the text/html content type.

So you can just:

from django.core.mail import send_mail
from django.template.loader import render_to_string


msg_plain = render_to_string('templates/email.txt', {'some_params': some_params})
msg_html = render_to_string('templates/email.html', {'some_params': some_params})

send_mail(
    'email title',
    msg_plain,
    '[email protected]',
    ['[email protected]'],
    html_message=msg_html,
)
Runaway answered 12/2, 2015 at 11:48 Comment(3)
Note if 'email.txt' and 'email.html' are in a directory templates as defined in settings than just do render_to_string('email.txt', {'some_params': some_params}_Shy
Good solution! However, with send_mail is not possible to set some custom header like i.e Return-Path which can be set with the EmailMultiAlternatives's constructor header parameterPsephology
'templates' needs to be removed from the file name in (at least in python 3.8) otherwise it yields a django.template.exceptions.TemplateDoesNotExist error. by default path is relative to the templates folderWesterfield
K
28

I have made django-templated-email in an effort to solve this problem, inspired by this solution (and the need to, at some point, switch from using django templates to using a mailchimp etc. set of templates for transactional, templated emails for my own project). It is still a work-in-progress though, but for the example above, you would do:

from templated_email import send_templated_mail
send_templated_mail(
        'email',
        '[email protected]',
        ['[email protected]'],
        { 'username':username }
    )

With the addition of the following to settings.py (to complete the example):

TEMPLATED_EMAIL_DJANGO_SUBJECTS = {'email':'hello',}

This will automatically look for templates named 'templated_email/email.txt' and 'templated_email/email.html' for the plain and html parts respectively, in the normal django template dirs/loaders (complaining if it cannot find at least one of those).

Knapp answered 16/3, 2011 at 22:50 Comment(2)
Looks good to me. I've trimmed this down and thrown it into a ticket to add django.shortcuts.send_templated_mail: code.djangoproject.com/ticket/17193Speechmaking
Cool, glad to see it get proposed as a tool for django core. My use-case/focus for the lib is a bit bigger than just the shortcut, (easy switching between mail providers that have key/value api's for mail sending), but it does feel like a missing feature from coreKnapp
B
24

I know this is an old question, but I also know that some people are just like me and are always looking for uptodate answers, since old answers can sometimes have deprecated information if not updated.

Its now January 2020, and I am using Django 2.2.6 and Python 3.7

Note: I use DJANGO REST FRAMEWORK, the code below for sending email was in a model viewset in my views.py

So after reading multiple nice answers, this is what I did.

from django.template.loader import render_to_string
from django.core.mail import EmailMultiAlternatives

def send_receipt_to_email(self, request):

    emailSubject = "Subject"
    emailOfSender = "[email protected]"
    emailOfRecipient = '[email protected]'

    context = ({"name": "Gilbert"}) #Note I used a normal tuple instead of  Context({"username": "Gilbert"}) because Context is deprecated. When I used Context, I got an error > TypeError: context must be a dict rather than Context

    text_content = render_to_string('receipt_email.txt', context, request=request)
    html_content = render_to_string('receipt_email.html', context, request=request)

    try:
        #I used EmailMultiAlternatives because I wanted to send both text and html
        emailMessage = EmailMultiAlternatives(subject=emailSubject, body=text_content, from_email=emailOfSender, to=[emailOfRecipient,], reply_to=[emailOfSender,])
        emailMessage.attach_alternative(html_content, "text/html")
        emailMessage.send(fail_silently=False)

    except SMTPException as e:
        print('There was an error sending an email: ', e) 
        error = {'message': ",".join(e.args) if len(e.args) > 0 else 'Unknown Error'}
        raise serializers.ValidationError(error)

Important! So how does render_to_string get receipt_email.txt and receipt_email.html? In my settings.py, I have TEMPLATES and below is how it looks

Pay attention to DIRS, there is this line os.path.join(BASE_DIR, 'templates', 'email_templates') .This line is what makes my templates accessible. In my project_dir, I have a folder called templates, and a sub_directory called email_templates like this project_dir->templates->email_templates. My templates receipt_email.txt and receipt_email.html are under the email_templates sub_directory.

TEMPLATES = [
{
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'templates', 'email_templates')],
    'APP_DIRS': True,
    'OPTIONS': {
        'context_processors': [
            'django.template.context_processors.debug',
            'django.template.context_processors.request',
            'django.contrib.auth.context_processors.auth',
            'django.contrib.messages.context_processors.messages',
        ],
    },
},
]

Let me just add that, my recept_email.txt looks like this;

Dear {{name}},
Here is the text version of the email from template

And, my receipt_email.html looks like this;

Dear {{name}},
<h1>Now here is the html version of the email from the template</h1>
Broomfield answered 6/1, 2020 at 20:36 Comment(0)
E
17

Use EmailMultiAlternatives and render_to_string to make use of two alternative templates (one in plain text and one in html):

from django.core.mail import EmailMultiAlternatives
from django.template import Context
from django.template.loader import render_to_string

c = Context({'username': username})    
text_content = render_to_string('mail/email.txt', c)
html_content = render_to_string('mail/email.html', c)

email = EmailMultiAlternatives('Subject', text_content)
email.attach_alternative(html_content, "text/html")
email.to = ['[email protected]']
email.send()
Evolutionary answered 31/1, 2014 at 2:56 Comment(0)
M
7

I have created Django Simple Mail to have a simple, customizable and reusable template for every transactional email you would like to send.

Emails contents and templates can be edited directly from django's admin.

With your example, you would register your email :

from simple_mail.mailer import BaseSimpleMail, simple_mailer


class WelcomeMail(BaseSimpleMail):
    email_key = 'welcome'

    def set_context(self, user_id, welcome_link):
        user = User.objects.get(id=user_id)
        return {
            'user': user,
            'welcome_link': welcome_link
        }


simple_mailer.register(WelcomeMail)

And send it this way :

welcome_mail = WelcomeMail()
welcome_mail.set_context(user_id, welcome_link)
welcome_mail.send(to, from_email=None, bcc=[], connection=None, attachments=[],
                   headers={}, cc=[], reply_to=[], fail_silently=False)

I would love to get any feedback.

Maggoty answered 10/8, 2018 at 14:12 Comment(2)
It would help a lot if you upload a demo application of your package on your repo.Foltz
Hi @Foltz thanks for this suggestion, I add it to the list of improvements !Maggoty
B
4

send_emai() didn't work for me so I used EmailMessage here in django docs.

I have included two versions of the anser:

  1. With html email version only
  2. With plain text email and html email versions
from django.template.loader import render_to_string 
from django.core.mail import EmailMessage

# import file with html content
html_version = 'path/to/html_version.html'

html_message = render_to_string(html_version, { 'context': context, })

message = EmailMessage(subject, html_message, from_email, [to_email])
message.content_subtype = 'html' # this is required because there is no plain text email version
message.send()

If you want to include a plain text version of your email, modify the above like this:

from django.template.loader import render_to_string 
from django.core.mail import EmailMultiAlternatives # <= EmailMultiAlternatives instead of EmailMessage

plain_version = 'path/to/plain_version.html' # import plain version. No html content
html_version = 'path/to/html_version.html' # import html version. Has html content

plain_message = render_to_string(plain_version, { 'context': context, })
html_message = render_to_string(html_version, { 'context': context, })

message = EmailMultiAlternatives(subject, plain_message, from_email, [to_email])
message.attach_alternative(html_message, "text/html") # attach html version
message.send()

My plain and html versions look like this: plain_version.html:

Plain text {{ context }}

html_version.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 ...
 </head>
<body>
<table align="center" border="0" cellpadding="0" cellspacing="0" width="320" style="border: none; border-collapse: collapse; font-family:  Arial, sans-serif; font-size: 14px; line-height: 1.5;">
...
{{ context }}
...
</table>
</body>
</html>
Byrle answered 25/6, 2020 at 19:25 Comment(1)
Your first solution worked like a charm for me. I didn't want anything to do with the txt file so I've used the EmailMessage class. Thanks for this awesome solution mate! :-)Unwieldy
G
3

There is an error in the example.... if you use it as written, the following error occurs:

< type 'exceptions.Exception' >: 'dict' object has no attribute 'render_context'

You will need to add the following import:

from django.template import Context

and change the dictionary to be:

d = Context({ 'username': username })

See http://docs.djangoproject.com/en/1.2/ref/templates/api/#rendering-a-context

Giannini answered 3/1, 2011 at 19:10 Comment(0)
T
3

Django Mail Templated is a feature-rich Django application to send emails with Django template system.

Installation:

pip install django-mail-templated

Configuration:

INSTALLED_APPS = (
    ...
    'mail_templated'
)

Template:

{% block subject %}
Hello {{ user.name }}
{% endblock %}

{% block body %}
{{ user.name }}, this is the plain text part.
{% endblock %}

Python:

from mail_templated import send_mail
send_mail('email/hello.tpl', {'user': user}, from_email, [user.email])

More info: https://github.com/artemrizhov/django-mail-templated

Trehala answered 5/10, 2012 at 13:56 Comment(5)
Hi, how can I set all my recipients to BCC?Compensation
@Compensation This is just a wrapper around the standard EmailMessage class of Django. So you should read the official documentation when looking for such features: docs.djangoproject.com/en/1.10/topics/email Also take a look at similar question: #3470672Trehala
To be more precise, the standard EmailMessage is not wrapped, but inherited. I.e. this is extension for the standard class :)Trehala
Possible to include JS/CSS in template?Barnette
Not sure if it will work for most email readers if you insert inline js/css into html body, but this is possible, why not.Trehala
P
0

I like using this tool to permit easily to send email HTML and TXT with easy context processing: https://github.com/divio/django-emailit

Paediatrician answered 20/4, 2017 at 15:35 Comment(0)
T
0

I wrote a snippet that allows you to send emails rendered with templates stored in the database. An example:

EmailTemplate.send('expense_notification_to_admin', {
    # context object that email template will be rendered with
    'expense': expense_request,
})
Tricrotic answered 17/2, 2018 at 12:3 Comment(0)
V
0

If you want dynamic email templates for your mail then save the email content in your database tables. This is what i saved as HTML code in database =

<p>Hello.. {{ first_name }} {{ last_name }}.  <br> This is an <strong>important</strong> {{ message }}
<br> <b> By Admin.</b>

 <p style='color:red'> Good Day </p>

In your views:

from django.core.mail import EmailMultiAlternatives
from django.template.loader import get_template

def dynamic_email(request):
    application_obj = AppDetails.objects.get(id=1)
    subject = 'First Interview Call'
    email = request.user.email
    to_email = application_obj.email
    message = application_obj.message

    text_content = 'This is an important message.'
    d = {'first_name': application_obj.first_name,'message':message}
    htmly = FirstInterviewCall.objects.get(id=1).html_content #this is what i have saved previously in database which i have to send as Email template as mentioned above HTML code

    open("partner/templates/first_interview.html", "w").close() # this is the path of my file partner is the app, Here i am clearing the file content. If file not found it will create one on given path.
    text_file = open("partner/templates/first_interview.html", "w") # opening my file
    text_file.write(htmly) #putting HTML content in file which i saved in DB
    text_file.close() #file close

    htmly = get_template('first_interview.html')
    html_content = htmly.render(d)  
    msg = EmailMultiAlternatives(subject, text_content, email, [to_email])
    msg.attach_alternative(html_content, "text/html")
    msg.send()

This will send the dynamic HTML template what you have save in Db.

Vaivode answered 26/7, 2018 at 4:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.