4. Usage

4.1. mail.send()

mail.send() is one of the most important function in this library. It is used to send one email to a list of recipients. It takes these arguments:

mail.send() arguments

Argument

Type

Required

Description

recipients

str | List[str | EmailAddress]

Yes

List of recipient email addresses

sender

str

No

Defaults to settings.DEFAULT_FROM_EMAIL

subject

str (context vars allowed)

No

Subject of Email, if template is not specified

message

str (context vars allowed)

No

Content of Email, if template is not specified

html_message

str (context vars allowed)

No

HTML content of Email, if template is not specified

emailmerge

str | EmailMerge

No

EmailMerge instance or name

language

str

No

Language (code) in which you want to send email. Defaults to settings.LANGUAGE_CODE.

cc

List[str | EmailAddress]

No

List of emails in cc(Carbon copy) field

bcc

List[str | EmailAddress]

No

List of emails in bcc(Blind carbon copy) field

attachments

dict

No

Email attachments - a dict where the keys are the filenames and the values are files, file-like-objects or path to file

context

dict

No

Dictionary where keys are strings and values are any serializable objects. Used to render email

headers

dict

No

Extra headers on the message

scheduled_time

datetime | date

No

Indicates when the message should be sent

expires_at

datetime | date

No

If specified, mails that are not yet sent won’t be delivered after this date.

expires_at

datetime | date

No

If specified, mails that are not yet sent won’t be delivered after this date.

priority

str

No

high, medium, low or now (send immediately)

backend

str

No

Alias of the backend you want to use from settings.SENDMAIL['BACKENDS']. Defaults to default

Note: Some arguments can take strings with special variables allowed. Examples of special variables include: #var1#, #recipient.first_name#, etc.

from sendmail import mail

mail.send(
    [EmailAddress.objects.create(email='peter@gmail.com', first_name='Peter'), 'lena@email.com', 'ben@yahoo.com'],
    'from@example.com',
    subject='My email',
    message='Hi there!',
    html_message='Hi <strong>#where#</strong>!',
    context={'where': 'there'},
    cc=['cc1@email.com'],
    bcc=['bcc1@email.com', 'bcc2@email.com']
)

The command above will queue 1 email specified lists of recipients. HTML message will be equal to Hi there!

Passing now as the priority allows to bypass the queue and deliver the email right away.

from sendmail import mail

 mail.send(
     'recipient@example.com', # List of email addresses or list of EmailAddress also accepted
     'from@example.com',
     emailmerge='your-template-here', # Could be an EmailMergeModel instance or name
     context={'generator': 'sendmail',
     'username': 'michaelpoi',}, # Context is used to fill both {{ var }} in html and #var# in ckeditor.
     language='en', # If not specified settings.LANGUAGE_CODE is used,
     priority='now'
 )

4.2. EmailAddress and recipient context

In the sendmail recipients are stored as EmailAddress model instances. This was done to allow personalization of emails. EmailAddress model has the following attributes:

EmailAddress attributes

Attribute

Type

Req.

Signature text

Signature html

Description

email

str

Yes

#recipient.email#

{{ recipient.email }}

Recipient email address, can also be a display name (Johh <johh@email.com>)

first_name

str

No

#recipient.first_name#

{{ recipient.first_name }}

Recipient first name.

last_name

str

No

#recipient.last_name#

{{ recipient.last_name }}

Recipient surname.

gender

str

No

#recipient.gender#

{{ recipient.gender }}

Recipient gender. Can be male, female or other. Is useful to generate greetings in HTML templates.

preferred_language

str

No

-

{{ recipient.preferred_language }}

Recipient preferred_language. If using mail.send_many() without language argument email to a certain user will be translated. If specified here language is not in settings.LANGUAGES default will be used.

is_blocked

bool

No

-

-

Defaults to False. If set to True recipient wont get any emails, no matter with mail.send() or mail.send_many()

Every time you use mail.send() or mail.send_many() list of recipients and cc or bcc (only for mail.send() ) are transformed to a list of EmailAddress instances. If recipient is in database it just selects it by email, otherwise creates a new instance with None for all non-required fields.

Recipient context is always passed to extend email context, however:

  • If you use mail.send() only 1 email is generated, so the context for the first recipient in a list is used to render email.

  • If you use mail.send_many() recipient context is passed to all emails generated.

Recipient context can be used in all phases of template creation (see more Templating). For example you can add to html template something like this:

{% with gender=recipient.gender %}
        {% if gender == 'male' %}
            Mr.
            {% elif gender == 'female' %}
            Ms.
            {% else %}
            Human
        {% endif %}
    {% endwith %}
{{ recipient.first_name }} {{ recipient.last_name }}

This way you can achieve personalized greeting for each recipient when using mail.send_many().

You can use this context when filling subject, content or placeholders values in CKEditor fields as well. For example:

from sendmail import mail
from sendmail.models import EmailAddress

john = EmailAddress.objects.create(email='john.doe@email.com',
                                   first_name='John',
                                   last_name='Doe')

mail.send(
    'john.doe@email.com',
    'from@example.com',
    subject='Message for #recipient.first_name#',
    html_message = '<h1>#recipient.first_name# #recipient.last_name#</h1>'
)

4.2.1. Overriding EmailAddress

If you want to add your custom fields to EmailAddress and use it in recipients context for email personalization, you can define your own swapping model based on EmailAddress:

In models.py of your app:

from sendmail.models.base import AbstractEmailAddress
from django.db import models
from sendmail.mixins import SwappableMetaMixin

class CustomEmailAddress(AbstractEmailAddress, SwappableMetaMixin):
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    # Any custom fields

Note that your app has to be listed in INSTALLED_APPS.

Then, you need to specify your custom model using EMAIL_ADDRESS_MODEL setting in settings.py

EMAIL_ADDRESS_MODEL = 'custom_user.CustomEmailAddress'

The default value is sendmail.EmailAddress.

Now if you restart your server you will be able to see updated model in admin interface and fill it with your data. Also, your entered data will be passed to templates and can be used like {{ recipient.field_name }} in html and #recipient.field_name# in rich contents.

If you have to get active model class:

from sendmail.settings import get_email_address_model

EmailAddress = get_email_address_model()

print(EmailAddress.objects.first()) # Use your model

Warning

It is not recommended to swap models in an operational database, as doing so may lead to inconsistencies, particularly in ForeignKey relationships. Swapping models can break existing references and constraints, resulting in data integrity issues. Always ensure that appropriate migrations and data adjustments are in place before making such changes.

4.3. mail.send_many()

mail.send_many() is one of the most important function in the library. It is used to generate n (number of recipients) emails (one for each recipient in recipients). mail.send_many() is much more efficient alternative for mail.send() , because it utilizes much less database queries. Using mail.send_many() you can maximize personalization like discussed in section above. mail.send_many() takes the same set of parameters like mail.send() , except:

  • cc and bcc can not be used in mail.send_many()

  • priority can not be now

Other parameters are shared among generated emails.

import tempfile
from sendmail import mail
from sendmail.models import EmailAddress

lena = EmailAddress.objects.create(email='lena@email.com', first_name='Lena')
ben = EmailAddress.objects.create(email='ben@yahoo.com', first_name='Ben', is_blocked=True)

with tempfile.NamedTemporaryFile(delete=True) as f:
    f.write(b'Testing attachments')
    f.seek(0)

    mail.send_many(
        recipients=[EmailAddress.objects.create(email='bob@gmail.com', first_name='Bob'), 'lena@email.com', 'ben@yahoo.com'],
        sender='from@email.com',
        subject='Hello #recipient.first_name#',
        message='This is a letter #id#',
        context={'id': 453},
        language='en',
        attachments={'new_test.txt': f},
    )

Running this will result in 2 emails queued (because user ben is_blocked and hence is excluded). Subjects will be personalized as “Hello Bob” and “Hello Lena”. Content will be the same: “This is a letter 453”. Both emails have the same attachment.

4.4. Templating

sendmail introduces a two-phase approach for creating email templates. This process ensures a flexible and powerful way to handle email templates, leveraging both HTML expertise and user-friendly editing tools.

  1. HTML Base File Creation

    In the first phase, experienced email HTML developers create base files while adhering to the specific limitations of rendering emails in various clients. During this phase, developers can:

    • Embed images using the {% inline_image %}(see more Inlines) template tag.

    • Insert placeholders using the {% placeholder %} template tag, which will be filled in the second phase.

These base files act as a foundation for further customization.

  1. CKEDITOR Placeholders editor

    Once the base file is ready, users can move on to the second phase. Using the admin interface, they select the base file and fill in the placeholders defined in the previous phase. In this phase, users can:

    • Create rich content such as lists, tables, headers, and more features allowed by the configuration in settings.CKEDITOR_CONFIGS.

    • Embed images, which will automatically be converted to a suitable format for sending via email.

This two-step process provides both technical flexibility for developers and ease of use for non-technical users.

4.4.1. HTML Base File Creation

Base Files should be stored in settings.TEMPLATES['DIRS'] / 'email'. sendmail looks for email folders in all specified DIRS.

In each of your base files you should load sendmail to use custom tags, which can be done as following:

{% load sendmail %}

In your templates you can specify variables to be filled with the context:

{% load sendmail %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Example email template</title>
</head>
<body>
    Hello, {{ username }}
    {% placeholder 'main' %}
</body>
</html>

username variable is expected then to be filled with mail.send() or mail.send_many() context. If it wont be passed user wont see any errors. You can still handle this using django build-in filters, for example:

Hello, {{ username|default:'user'}}

In your templates you may want to use placeholders inside conditions, loops or includes. With sendmail it is possible.

main.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% if True %}
{% placeholder 'basic1' %}
{% placeholder 'basic2' %}
    {% else %}
    {% placeholder 'basic3' %}
{% endif %}
{% include 'email/in.html' %}

</body>
</html>

in.html

{% load sendmail %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% placeholder 'include1' %}
{% placeholder 'include2' %}

</body>
</html>

All placeholders in the previous example will be parsed successfully and provided for users.

Warning

Placeholders are not recognized in child templates when using the Django {% extends %} tag.

4.4.2. Inlines

You may want to use embed images to your templates. This can be done using sendmail {% inline_image %} template tag.

<img src="{% inline_image 'images/logo.png' %}" alt="" width="100">

You can specify either alias or absolute path to your image. Alias are resolved:

1. If DEBUG mode is turned on searches in static folder. If not found searches in staticfiles. If not found raises FileNotFound exception.

  1. If DEBUG is off searches only in staticfiles. If not found src of your inline image will be empty.

4.4.2.1. Media Images

All images inserted in CKEditor fields will be saved in MEDIA_ROOT of your project. If you for any reason want to insert media images into your html templates:

<img src="{% inline_media_image 'images/logo.png' %}" alt="" width="100">

4.4.3. CKEDITOR Placeholders editor

When needed base file was created, users can create 2-phase templates using it. For it you should simply:

  1. Open admin interface and click create new EmailMergeModel.

  2. Enter a name which will be used as an template alias for sending.

  3. Click “Save and continue editing” (This event is also triggered when a template file is changing)

  4. Forms for placeholders editing will appear with defaults, such as:

    Placeholder: <name>, Language: <lang_code>

5. Fill these placeholders with your rich content (you can include variables like #var#, #price#, etc. or recipients context (see more EmailAddress and recipient context))

4.5. Multilingual Templates

In sendmail you can create and send templates in multiple languages. For this simply edit your settings.py:

Verify that internalization is enabled:

USE_I18N = True

Default templates language can be changed in settings.LANGUAGE_CODE

LANGUAGE_CODE = 'en'

List of all translation languages should be specified in settings.LANGUAGES

LANGUAGES = [
('en', 'English'),
('de', 'German'),
]

Adjust this as needed.

The default language will be used when:

  1. Language for mail.send() is not provided or is not valid (not in LANGUAGES)

  2. if mail.send_many() language is not set and recipient preferred language is None or not valid

If mail.send_many() is called with defined language then all the emails will be forced to that language. Otherwise each email is translated to recipient preferred language if it is available. Extra attachments are also translated to this language.

from sendmail.mail import send_many
from sendmail.models import EmailAddress

en_recipient = EmailAddress.objects.create(email='en@gmail.com', first_name='John', preferred_language='en')
de_recipient = EmailAddress.objects.create(email='de@gmail.com', first_name='Ali', preferred_language='de')

send_many(recipients=[en_recipient, de_recipient], emailmerge='your-template', language='en')

In this case de_recipient also gets English copy of an email. To use preferred language you can do something like this:

from sendmail.mail import send_many
from sendmail.models import EmailAddress

en_recipient = EmailAddress.objects.create(email='en@gmail.com', first_name='John', preferred_language='en')
de_recipient = EmailAddress.objects.create(email='de@gmail.com', first_name='Ali', preferred_language='de')

send_many(recipients=[en_recipient, de_recipient], emailmerge='your-template')

Now de_recipient gets German letter and en_recipient English copy.

4.6. Custom Email Backends

By default sendmail uses django.core.mail.backends.smtp.EmailBackend. If you want to use other email backends, you can change it by configuring settings.SENDMAIL['BACKENDS']

For example to use django-ses you can do:

SENDMAIL = {
# other settings
'BACKENDS': {
    'default': 'django.core.mail.backends.smtp.EmailBackend',
    'ses': 'django_ses.SESBackend',
    }
}

Now when you use mail.send() or mail.send_many() you can which backend will be used for sending by specifying backend argument. If backend is not specified default will be used.

Note For mail.send_many() all generated emails will inherit backend argument.

from sendmail import mail

mail.send(
['recipient@example.com'],
'from@example.com',
subject='Hello',
)

Resulting email will be sent using default backend.

from sendmail import mail

mail.send_many(
recipients=['recipient@example.com', 'next@gmail.com'],
sender='from@example.com',
subject='Hello',
backend='ses'
)

Resulting 2 emails will be sent using django-ses backend.

4.7. Management commands

Sendmail commands are available under sendmail namespace and can be triggered as following:

python manage.py sendmail <subcommand> [arguments]

example: python manage.py all -p 4

  • python manage.py sendmail -h - Show help message.

  • python manage.py sendmail –version - Show installed version of sendmail.

  • all - send all queued emails, those are not successfully sent are marked as failed or requeued depending on Settings.

  • batch - send one batch of queued emails. Batch size is defined in settings(Batch Size).

all and batch arguments

Argument

Description

–processes or -p

Number of concurrent processes to send queued emails. Defaults to 1.

–log-level or -l

Log level 0 to log nothing, 1 to log only errors. Defaults to 2 - log everything.

  • cleanup_mail - delete all emails created before an X number of days (defaults to 90).

cleanup_mail arguments

Argument

Description

–days or -d

Email older than this argument will be deleted. Defaults to 90.

–delete-attachments or -da

Flag to delete orphaned attachment records and files on disk. If not specified attachments wont be deleted.

–batch-size or -b

Limits number of emails being deleted in a batch. Defaults to 1000.

  • dblocks - when sendmail is sending emails using all or batch management command it blocks the entire database. You can use this command to manage these DB locks.

dblocks

Argument

Description

–delete or -d

Delete expired locks.

–delete-all

Delete all locks.

4.8. Newsletter

Alternatively to calling send you can enqueue emails directly from admin interface using Newsletters.

For this you can create RecipientLists, which are just named lists of EmailAddress objects.

As soon as your RecipientList is created you can create and send your Newsletter.

For this simply create new Newsletter object:

  • Set name, RecipientList and choose EmailMerge to be used.

    Sendmail has its own context parser. To use it simply click Reparse Context button, you should reparse it everytime you change the EmailMerge.

  • Parser will generate the structure of expected context, you can then fill values or add objects to the list.

Note

Note, you wont see recipient context keys, because those are filled automatically for each recipient.

  • You can specify other optional parameters which will be passed to each created email.

  • When you are ready just click Send and your emails will be created and can be tracked on model overview admin page.

  • If your emails failed or you want to completely resend a Newsletter you can use one of the admin actions.

4.8.1. Email Tracking

Emails generated with Newsletter feature can be tracked to measure recipients engagement. Currently sendmail can track:

  • When email was opened.

  • When interaction button was clicked.

Tracking is disabled by default. To make it work add to your settings.py:

SENDMAIL = {
    'TRACKING_ENABLED': True,
    'TRACKING_DOMAIN': 'https://www.example.com',
    ...
}

Now you can use templatetags for tracking:

  • {% tracker_link <target_img> %} should be used as a src for any image on a HTML template:

    <img src="{% tracker_link 'images/logo.jpg'%}" alt="No image"/>
    

    You can provide a link to any image in media or staticfiles that will be loaded, updating EmailModel opened_at.

    Note

    Most email clients prompt the user to confirm before loading images from external sources. So this metric can be not reliable enough.

  • {% click_link <target_url> %} can be used in href of button or a you want to measure interactions with.

    <td> <a href="{% click_link 'https://www.google.com' %}" target="_blank">Call To Action</a> </td>
    

    After clicking a button EmailModel clicked_at will be updated and user will be redirected to target_url.

Note

In the EmaiModel, the fields opened_at and clicked_at record the timestamps of the first instances when the user opens or interacts with the email, respectively.

If you want to extend the standard behavior, you can connect to email_opened and email_clicked signals. Signals are triggered every time email is opened or clicked.