[django] Creating email templates with Django

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?

This question is related to django email django-email django-mailer

The answer is


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


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,
})

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


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>

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


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.


Boys and Girls!

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,
)

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()

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.


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


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>

Examples related to django

How to fix error "ERROR: Command errored out with exit status 1: python." when trying to install django-heroku using pip Pylint "unresolved import" error in Visual Studio Code Is it better to use path() or url() in urls.py for django 2.0? Unable to import path from django.urls Error loading MySQLdb Module 'Did you install mysqlclient or MySQL-python?' ImportError: Couldn't import Django Django - Reverse for '' not found. '' is not a valid view function or pattern name Class has no objects member Getting TypeError: __init__() missing 1 required positional argument: 'on_delete' when trying to add parent table after child table with entries How to switch Python versions in Terminal?

Examples related to email

Monitoring the Full Disclosure mailinglist require(vendor/autoload.php): failed to open stream Failed to authenticate on SMTP server error using gmail Expected response code 220 but got code "", with message "" in Laravel How to to send mail using gmail in Laravel? Laravel Mail::send() sending to multiple to or bcc addresses Getting "The remote certificate is invalid according to the validation procedure" when SMTP server has a valid certificate How to validate an e-mail address in swift? PHP mail function doesn't complete sending of e-mail How to validate email id in angularJs using ng-pattern

Examples related to django-email

Creating email templates with Django

Examples related to django-mailer

RuntimeWarning: DateTimeField received a naive datetime Creating email templates with Django