Advanced usage

Plain text and HTML messages

All messages that you create with mail_templated are instances of the mail_templated.EmailMessage class which extends django.core.mail.EmailMultiAlternatives. This does not mean all messages are multipart messages by default, but they become so if both body and html template blocks are not empty in the template rendering output.

{% extends "mail_templated/base.tpl" %}

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

{% block body %}
This is a plain text part.
{% endblock %}

{% block html %}
This is an <strong>html</strong> part.
{% endblock %}

In this case the body part goes to the body of the email message, and the html part is attached as alternative.

If you define only one of html and body blocks, it goes to the body of email message with appropriate content type text/plain or text/html.

{% extends "mail_templated/base.tpl" %}

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

{% block html %}
This is an <strong>html</strong> message.
{% endblock %}

The template above produces an HTML message without a plain text alternative.

Unused block is empty by default, because it is defined empty in the base template. If you override both blocks but one of them is rendered as empty string, this produces the same result as if the block is not used at all. For example, let’s review this template:

{% extends "mail_templated/base.tpl" %}

{% block subject %}Subject{% endblock %}
{% block body %}{{ plaintext_message }}{% endblock %}
{% block html %}{{ html_message }}{% endblock %}

If the plaintext_message variable is empty then mail_templated will create a message without the plain text part. This way you can be sure the users will not see empty messages. However, only newlines are truncated from the email parts (this is done for convenient template formatting). If the part contains just one space character then it is considered as non-empty.

Default subject and body

Both the mail_templated.send_mail() function and the mail_templated.EmailMessage class got new parameters template_name and context instead of (good) old subject and body. However you can still pass both old parameters as keyword arguments. In this case they will be treated as default values. If there is no appropriate part in the message template (or it is empty) then the default value will be used.

Let’s review this template without subject:

{% extends "mail_templated/base.tpl" %}

{% block body %}
This is a plain text message.
{% endblock %}

Now pass default subject and body to the send_mail() function:

from mail_templated import send_mail

send_mail('email/without_subject.tpl', {},
          'from@inter.net', ['to@inter.net'],
          subject='Default subject', body='Default subject')

The default subject will be used since there is no subject in the template. However the default body will be replaced by the value from the template. The html part also overrides default body if the body part is empty.

Base email template and inheritance

The email template is rendered as a solid document, and all email message parts (subject, body and html) appears concatenated after rendering. For this purpose the base template mail_templated/base.tpl contains special markers for the email message parts, so that they can be found and extracted after rendering.

This approach eliminates the dependency on the inner implementation of the Django template engine. It would be a bad idea to extract and render the blocks objects separately, because the template engine implementation tends to change. But anyway you should not worry about that markup in normal situation. Extend the base template provided by mail_templated and use the template blocks as usually.

You can define your own base template. Just ensure your base template extends the base of the base email templates, and any content is defined inside of blocks subject, body and html.

templates/email/base.html

{% extends "mail_templated/base.tpl" %}

{% block subject %}
{{ COMPANY_NAME }} {% block subject_content %}{% endblock %}
{% endblock %}

{% block html %}
  <img src="{{ COMPANY_LOGO_URL }}" />
  {% block body_content %}
    {% include 'templates/email/parts/standard_greetings.html' %}
  {% endblock %}
{% endblock %}

Now you can extend it as usually:

templates/email/news.html

{% extends "email/base.html" %}

{% block subject_content %}{{ article.title }}{% endblock %}

{% block body_content %}
  {{ block.super }}
  You probably will be interested in this news:
  {{ article.preview }}
{% endblock %}

As you can see, there is nothing special about template inheritance. Actually it is even not required to extend the base of the base email templates. The most base template is just a helper that adds markers to the template. It is better to use it if you want to be sure new version of the mail_templated app will be compatible with your code. But if you want, you can use your own base template with markers for the email parts.

The most base template looks like this:

mail_templated/base.tpl

{{ TAG_START_SUBJECT }}{% block subject %}{% endblock %}{{ TAG_END_SUBJECT }}

{{ TAG_START_BODY }}{% block body %}{% endblock %}{{ TAG_END_BODY }}

{{ TAG_START_HTML }}{% block html %}{% endblock %}{{ TAG_END_HTML }}

Let’s review a simple template as an example:

{% extends "mail_templated/base.tpl" %}

{% block subject %}This is a subject{% endblock %}
{% block body %}This is a plain text body{% endblock %}
{% block html %}This is an html body{% endblock %}

This will compile to:

###start_subject###This is a subject###end_subject###
###start_body###This is a plain text body###end_body###
###start_html###This is an html body###end_html###

You can see that final document contains special tags for the message parts. These markers is the main thing that the base template adds to your message. Instead of extending it, you can use the markers just in your template:

###start_subject###New for {{ week }}###end_subject###
###start_body###
Hellow, {{ username }}! Below is a list of news for {{ week }}.
###end_body###
###start_html###
Hellow, <strong>{{ username }}</strong>!
Below is a list of news for <strong>{{ week }}</strong>.
###end_html###

This is the most efficient and the most inflexible way to define your templates. They will be compiled fast, but there is a chance you will go home much later.

The format of these tags can be changed in settings. The str.format() method is used to format the tags. Please see the Format String Syntax docs if you need more info about formatting.

# Default value is '###{bound}_{block}###'
MAIL_TEMPLATED_TAG_FORMAT='<!--{block}:{bound}-->'
<!--subject:start-->This is a subject<!--subject:end-->
<!--body:start-->This is a plain text body<!--body:end-->
<!--html:start-->This is an html body<!--html:end-->

If there is any probability that the format will change in the future then you probably want to use some variables. mail_templated provides such variable to the context of your templates automatically.

{{ TAG_START_SUBJECT }}This is a subject{{ TAG_END_SUBJECT }}
{{ TAG_START_BODY }}This is a plain text body{{ TAG_END_BODY }}
{{ TAG_START_HTML }}This is an html body{{ TAG_END_HTML }}

You even can change the name format for these variables if the default format conflicts with your code or you just hate it for some personal reason (unfortunately there is no format for the names of these settings, I hope this is not so important really).

# Default value is 'TAG_{BOUND}_{BLOCK}'
MAIL_TEMPLATED_TAG_VAR_FORMAT='{BLOCK}_{BOUND}'
# TODO: Define format for the format of format.
{{ SUBJECT_START }}This is a subject{{ SUBJECT_END }}
{{ BODY_START }}This is a plain text body{{ BODY_END }}
{{ HTML_START }}This is an html body{{ HTML_END }}

Finally you may decide to define your own base template:

{{ SUBJECT_START }}{% block subject %}{% endblock %}{{ SUBJECT_END }}
{{ HTML_START }}{% block body %}{% endblock %}{{ HTML_END }}
{{ BODY_START }}
Please use a modern email client to see the html part of this message.
{{ BODY_END }}

or without these tag name variables:

<!--subject:start-->{% block subject %}{% endblock %}<!--subject:end-->
<!--body:start-->{% block body %}{% endblock %}<!--body:end-->
<!--html:start-->
Please use a modern email client to see the html part of this message.
<!--html:end-->

Don’t forget to add a test that checks the mail_templated app with your format of templates. Something like this would be fine:

from django.core import mail
from django.test import TestCase

from mail_templated import send_mail


class SendMailTestCase(TestCase):

    def test_plain(self):
        send_mail('email/test.tpl', {'name': 'User'},
                  'from@inter.net', ['to@inter.net'])
        self.assertEqual(len(mail.outbox), 1)
        message = mail.outbox[0]
        self.assertEqual(message.from_email, 'from@inter.net')
        self.assertEqual(message.to, ['to@inter.net'])
        self.assertEqual(message.subject, 'Message for User')
        self.assertEqual(message.body, 'Hello, User!')

Working with send_mail() function

You probably know that the API for Django's send_mail() function from django.core.mail is frozen. Any new code wanting to extend the functionality goes to the django.core.mail.EmailMessage class. The mail_templated.send_mail() function works almost exactly the same way as the standard one. But it is much more powerful than it seems at the first look. The magic is done by passing all extra keyword arguments to the mail_templated.EmailMessage class constructor, which then passes them to the base class django.core.mail.EmailMultiAlternatives. Thus you can use all those features that are accessible via parameters of the EmailMessage class constructor.

For example, you can add attachments like in this example:

send_mail(
    'email/message.tpl', context_dict, from_email, [email],
    attachments=[('attachment.png', content, 'image/png')])

The limitation of this feature is that you can’t attach a file from the file system. But if the content is in the variable already then this will work well for you.

You can attach alternatives the same way:

send_mail(
    'email/message.tpl', context_dict, from_email, [email],
    alternatives=[('HTML alternative', 'text/html')])

You can also specify cc, bcc, reply_to and extra headers. Please review the API documentations for detailed info about parameters:

Working with EmailMessage class

The mail_templated.EmailMessage class supports all the features that are supported by the django.core.mail.EmailMultiAlternatives class. And of course it provides ability to use templates. If you have a complex task that can not be done in one step then this class is probably what you need. In other case consider the send_mail() function.

The message instance may be initialized with many various parameters. The most common case will probably look like this:

message = EmailMessage('email/message.tpl', context, from_email, [email])

But you are free to create completely empty message and initialize it later.

message = EmailMessage()
message.template_name = 'email/message.tpl'
message.context = {'user_names': []}
message.from_email = from_email
message.to = []
for user in users:
    message.context['user_names'].append(user)
    message.to.append(user.email)

The EmailMessage class has all methods that are available in the base classes, so you can use this class in the usual way.

message.attach_alternative(html_content, 'text/html')
message.attach_file(image_file_name, 'image/png')

Finally just send the message when you are done.

message.send()

As you can see this is almost regular email message object. You just set template_name and context instead of subject and body, and all the work is done behind the scene. But in fact you have more control that you can use when needed. This will be described in the next sections.

Please review the API documentations for detailed info about parameters and attributes:

Loading and rendering the email template

The template that you specify via template_name on the EmailMessage class initialization is loaded and rendered automatically when you call the send() method. It tries to do this as late as possible. But you can take the control and force this at any time. The EmailMessage class provides two methods for this needs: load_template() and render().

The most fragmented approach looks like this:

message = EmailMessage()
message.template_name = 'email/message.tpl'
message.load_template()
message.render()
message.send()

You can pass the template name either to the constructor or to the load_template() method:

message = EmailMessage('email/message.tpl')
message.load_template()

message = EmailMessage()
message.load_template('email/message.tpl')

You even can load it manually and then set via the template property.

message = EmailMessage()
message.template = get_template('email/message.tpl')

And you even can omit the call to the load_template() method and just use the render() method only. When you try to render the template in any way, it will be loaded automatically if not loaded yet.

Before you render the template, a context dict should be provided. There are also few variants how you can do this.

message = EmailMessage('email/message.tpl', context)
message.render()

message = EmailMessage('email/message.tpl')
message.context = context
message.render()

message = EmailMessage('email/message.tpl')
message.render(context)

Finally you can pass render=True to the constructor if you want to render it immediately.

message = EmailMessage('email/message.tpl', context, render=True)

There is also nothing wrong (expect of efficiency) if you want to load and render one template, then load and render another one.

message = EmailMessage(customer.message_template_file, context,
                       from_email, [email])
message.render()
if not is_valid_html(message.body):
    message.load_template('email/fallback_message.tpl')
    message.context.update(fallback_extra_context)
    message.render()
message.send()

As you can see in this example, you can access the resulting subject and body as soon as the message is rendered.

message = EmailMessage('email/message.tpl', context, render=True)
logger.debug('Subject: ' + message.subject)
logger.debug('Body: ' + message.body)
if message.alternatives:
    logger.debug('Alternarive: ' + message.alternatives[0][0])

When rendered, the message object becomes very similar to the standard Django’s EmailMessage class. You can check current status via the is_rendered property.

Serialization

mail_templated supports the pickle module. You can serialize the message object at any stage. However what really makes sense is serializing before invoking the load_template() method or after invoking the render() method. If you decide to serialize just between the calls to these methods then you will lost the compiled template instance, because it can not be serialized with pickle.

Let’s play with the email object a little.

>>> import pickle
>>> from mail_templated import EmailMessage
>>> message = EmailMessage('email/message.tpl', {})

As soon as the message exists, you can serialize it.

>>> # Serialize the message.
>>> pickled_message = pickle.dumps(message)
>>> # Let's see how it looks now.
>>> print repr(pickled_message)
"ccopy_reg\n_reconstructor\np0\n(cmail_templated.message\nEmailMessage\np1\
nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nS'body'\np6\nNsS'extra_headers'
\np7\n(dp8\nsS'attachments'\np9\n(lp10\nsS'_is_rendered'\np11\nI00\nsS'cc'\
np12\n(lp13\nsS'template_name'\np14\nS'mail_templated_test/plain.tpl'\np15\
nsS'alternatives'\np16\n(lp17\nsS'bcc'\np18\n(lp19\nsS'to'\np20\n(lp21\nsS'
connection'\np22\nNsS'context'\np23\n(dp24\nsS'reply_to'\np25\n(lp26\nsS'fr
om_email'\np27\nS'webmaster@localhost'\np28\nsS'subject'\np29\nNsb."

Now you can store it somewhere for later use. Then load and de-serialize the message when needed, and it is ready for further processing.

>>> # De-serialize the message.
>>> message2 = pickle.loads(pickled_message)
>>> # Check the message state.
>>> print repr(message2)
<mail_templated.message.EmailMessage object at 0x7ffb8ad2e810>
>>> print repr(message2.template)
None
>>> # The template is not loaded yet. Load the template
>>> message2.load_template()
>>> # How is it now?
>>> print repr(message2.template)
<django.template.backends.django.Template object at 0x7ffb8a11c050>
>>> # Good! It's ready for rendering.

While the template is loaded, let’s try to serialize and de-serialize it again.

>>> # Serialize/de-serialize again.
>>> message3 = pickle.loads(pickle.dumps(message2))
>>> # Is the message still alive?
>>> print repr(message3)
<mail_templated.message.EmailMessage object at 0x7ffb8ad4f790>
>>> # Yes, it's still alive, that's good. What about the template?
>>> print repr(message3.template)
None
>>> # Ooops! We lost the template object. So now we have to load it again.
>>> message3.load_template()
>>> print repr(message3.template)
<django.template.backends.django.Template object at 0x7ffb8a0fdf10>
>>> # Phew! It's here now.

Actually if lost, the template will be loaded automatically again when you try to render it. You will not see any errors. Just your code will do some useless extra work.

>>> message4 = pickle.loads(pickle.dumps(message3))
>>> print repr(message4.template)
None
>>> # Oh no! We lost it again :(
>>> message4.render()
>>> # Hmm... There is no any error!
>>> print repr(message4.template)
<django.template.backends.django.Template object at 0x7ffb8a0b4d50>
>>> # Magic? No, this is by design!

So, remember to load the template just before the rendering, not before serialization.

Once rendered, you can serialize/de-serialize it again without problems.

>>> # Check the message state.
>>> print repr([message4.is_rendered, message4.subject, message4.body])
[True, u'Test subject', u'Test body']
>>> # Continue the tortures.
>>> message5 = pickle.loads(pickle.dumps(message4))
>>> # The author said it should work fine now.
>>> print repr(message5.template)
None
>>> # :`(
>>> # :```(
>>> # But wait!
>>> print repr([message5.is_rendered, message5.subject, message5.body])
[True, u'Test subject', u'Test body']
>>> # Heh, the template is not needed anymore! :D

There are so many combination how you can load, render and serialize the message, so that I’m afraid I can’t describe all of them here. These examples should help you to construct your own combination.

Cleanup for third-party libraries

There are many third-party libraries that help you to work with email messages. If a library can work with the standard django.core.mail.EmailMessage class then it probably can work without problems with mail_templated.EmailMessage. However some library may be surprised by the additional attributes on the email message object. For example, the Djrill app will pass your template_name to the Mandrill service because it provides it’s own template system, and it uses the template_name parameter too (what a surprise!).

If something similar happens to your messages then you should wipe out the tracks of the mail_templated app. The most easy way to do this is to delete the conflicting attributes. The EmailMessage class provides a convenient method clean() for this purpose. It removes the most expensive and risky properties - context, template and template_name.

If you use the send_mail() function then the cleanup is invoked for you automatically just after rendering. You can disable this behaviour by passing the clean=False keyword argument.

If you use the EmailMessage class then you should care of cleanup yourself. Fortunately there are many places where you can invoke the clean() method either directly or via special keyword argument clean.

# Invoke the cleanup right on the initialisation.
message = EmailMessage('email/message.tpl', {}, render=True, clean=True)
# Call the method manually after rendering.
message.render()
message.clean()
# Pass `clean=True` to the `render()` method.
message.render(clean=True)
# The `send()` method also supports this argument.
message.send(clean=True)

There is no much difference in these variants. Just choice one that makes your code clean and clear.