Skip to main content

Django Post Office is a simple app to send and manage your emails in Django. Some awesome features are:

  • Allows you to send email asynchronously

  • Multi backend support

  • Supports HTML email

  • Supports database based email templates

  • Built in scheduling support

  • Works well with task queues like RQ or Celery

  • Uses multiprocessing (and threading) to send a large number of emails in parallel

  • Supports multilingual email templates (i18n)

Dependencies

Installation

Build Status

  • Install from PyPI (or you manually download from PyPI):

    pip install django-post_office
  • Add post_office to your INSTALLED_APPS in django’s settings.py:

INSTALLED_APPS = (
    # other apps
    "post_office",
)
  • Run migrate:

    python manage.py migrate
  • Set post_office.EmailBackend as your EMAIL_BACKEND in django’s settings.py:

    EMAIL_BACKEND = 'post_office.EmailBackend'

Quickstart

Send a simple email is really easy:

from post_office import mail

mail.send(
    'recipient@example.com', # List of email addresses also accepted
    'from@example.com',
    subject='My email',
    message='Hi there!',
    html_message='Hi <strong>there</strong>!',
)

If you want to use templates, ensure that Django’s admin interface is enabled. Create an EmailTemplate instance via admin and do the following:

from post_office import mail

mail.send(
    'recipient@example.com', # List of email addresses also accepted
    'from@example.com',
    template='welcome_email', # Could be an EmailTemplate instance or name
    context={'foo': 'bar'},
)

The above command will put your email on the queue so you can use the command in your webapp without slowing down the request/response cycle too much. To actually send them out, run python manage.py send_queued_mail. You can schedule this management command to run regularly via cron:

* * * * * (/usr/bin/python manage.py send_queued_mail >> send_mail.log 2>&1)

or, if you use uWSGI as application server, add this short snipped to the project’s wsgi.py file:

from django.core.wsgi import get_wsgi_application

application = get_wsgi_application()

# add this block of code
try:
    import uwsgidecorators
    from django.core.management import call_command

    @uwsgidecorators.timer(10)
    def send_queued_mail(num):
        """Send queued mail every 10 seconds"""
        call_command('send_queued_mail', processes=1)

except ImportError:
    print("uwsgidecorators not found. Cron and timers are disabled")

Alternatively you can also use the decorator @uwsgidecorators.cron(minute, hour, day, month, weekday). This will schedule a task at specific times. Use -1 to signal any time, it corresponds to the * in cron.

Please note that uwsgidecorators are available only, if the application has been started with uWSGI. However, Django’s internal ./manange.py runserver also access this file, therefore wrap the block into an exception handler as shown above.

This configuration is very useful in environments, such as Docker containers, where you don’t have a running cron-daemon.

Usage

mail.send()

mail.send is the most important function in this library, it takes these arguments:

Argument

Required

Description

recipients

Yes

list of recipient email addresses

sender

No

Defaults to settings.DEFAULT_FROM_EMAIL, display name is allowed (John <john@a.com>)

subject

No

Email subject (if template is not specified)

message

No

Email content (if template is not specified)

html_message

No

HTML content (if template is not specified)

template

No

EmailTemplate instance or name

language

No

Language in which you want to send the email in (if you have multilingual email templates.)

cc

No

list emails, will appear in cc field

bcc

No

list of emails, will appear in bcc field

attachments

No

Email attachments - A dictionary where the keys are the filenames and the values are either:

  • files

  • file-like objects

  • full path of the file

context

No

A dictionary, used to render templated email

headers

No

A dictionary of extra headers on the message

scheduled_time

No

A date/datetime object indicating when the email should be sent

priority

No

high, medium, low or now (send_immediately)

backend

No

Alias of the backend you want to use. default will be used if not specified.

render_on_delivery

No

Setting this to True causes email to be lazily rendered during delivery. template is required when render_on_delivery is True. This way content is never stored in the DB. May result in significant space savings.

Here are a few examples.

If you just want to send out emails without using database templates. You can call the send command without the template argument.

from post_office import mail

mail.send(
    ['recipient1@example.com'],
    'from@example.com',
    subject='Welcome!',
    message='Welcome home, {{ name }}!',
    html_message='Welcome home, <b>{{ name }}</b>!',
    headers={'Reply-to': 'reply@example.com'},
    scheduled_time=date(2014, 1, 1),
    context={'name': 'Alice'},
)

post_office is also task queue friendly. Passing now as priority into send_mail will deliver the email right away (instead of queuing it), regardless of how many emails you have in your queue:

from post_office import mail

mail.send(
    ['recipient1@example.com'],
    'from@example.com',
    template='welcome_email',
    context={'foo': 'bar'},
    priority='now',
)

This is useful if you already use something like django-rq to send emails asynchronously and only need to store email related activities and logs.

If you want to send an email with attachments:

from django.core.files.base import ContentFile
from post_office import mail

mail.send(
    ['recipient1@example.com'],
    'from@example.com',
    template='welcome_email',
    context={'foo': 'bar'},
    priority='now',
    attachments={
        'attachment1.doc': '/path/to/file/file1.doc',
        'attachment2.txt': ContentFile('file content'),
        'attachment3.txt': { 'file': ContentFile('file content'), 'mimetype': 'text/plain'},
    }
)

Template Tags and Variables

post-office supports Django’s template tags and variables. For example, if you put “Hello, {{ name }}” in the subject line and pass in {'name': 'Alice'} as context, you will get “Hello, Alice” as subject:

from post_office.models import EmailTemplate
from post_office import mail

EmailTemplate.objects.create(
    name='morning_greeting',
    subject='Morning, {{ name|capfirst }}',
    content='Hi {{ name }}, how are you feeling today?',
    html_content='Hi <strong>{{ name }}</strong>, how are you feeling today?',
)

mail.send(
    ['recipient@example.com'],
    'from@example.com',
    template='morning_greeting',
    context={'name': 'alice'},
)

# This will create an email with the following content:
subject = 'Morning, Alice',
content = 'Hi alice, how are you feeling today?'
content = 'Hi <strong>alice</strong>, how are you feeling today?'

Multilingual Email Templates

You can easily create email templates in various different languanges. For example:

template = EmailTemplate.objects.create(
    name='hello',
    subject='Hello world!',
)

# Add an Indonesian version of this template:
indonesian_template = template.translated_templates.create(
    language='id',
    subject='Halo Dunia!'
)

Sending an email using template in a non default languange is also similarly easy:

mail.send(
    ['recipient@example.com'],
    'from@example.com',
    template=template, # Sends using the default template
)

mail.send(
    ['recipient@example.com'],
    'from@example.com',
    template=template,
    language='id', # Sends using Indonesian template
)

Custom Email Backends

By default, post_office uses django’s smtp.EmailBackend. If you want to use a different backend, you can do so by configuring BACKENDS.

For example if you want to use django-ses:

POST_OFFICE = {
    'BACKENDS': {
        'default': 'smtp.EmailBackend',
        'ses': 'django_ses.SESBackend',
    }
}

You can then choose what backend you want to use when sending mail:

# If you omit `backend_alias` argument, `default` will be used
mail.send(
    ['recipient@example.com'],
    'from@example.com',
    subject='Hello',
)

# If you want to send using `ses` backend
mail.send(
    ['recipient@example.com'],
    'from@example.com',
    subject='Hello',
    backend='ses',
)

Management Commands

  • send_queued_mail - send queued emails, those aren’t successfully sent will be marked as failed. Accepts the following arguments:

Argument

Description

--processes or -p

Number of parallel processes to send email. Defaults to 1

--lockfile or -L

Full path to file used as lock file. Defaults to /tmp/post_office.lock

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

You may want to set these up via cron to run regularly:

* * * * * (cd $PROJECT; python manage.py send_queued_mail --processes=1 >> $PROJECT/cron_mail.log 2>&1)
0 1 * * * (cd $PROJECT; python manage.py cleanup_mail --days=30 >> $PROJECT/cron_mail_cleanup.log 2>&1)

Settings

This section outlines all the settings and configurations that you can put in Django’s settings.py to fine tune post-office’s behavior.

Batch Size

If you may want to limit the number of emails sent in a batch (sometimes useful in a low memory environment), use the BATCH_SIZE argument to limit the number of queued emails fetched in one batch.

# Put this in settings.py
POST_OFFICE = {
    'BATCH_SIZE': 50
}

Default Priority

The default priority for emails is medium, but this can be altered by setting DEFAULT_PRIORITY. Integration with asynchronous email backends (e.g. based on Celery) becomes trivial when set to now.

# Put this in settings.py
POST_OFFICE = {
    'DEFAULT_PRIORITY': 'now'
}

Log Level

The default log level is 2 (logs both successful and failed deliveries) This behavior can be changed by setting LOG_LEVEL.

# Put this in settings.py
POST_OFFICE = {
    'LOG_LEVEL': 1 # Log only failed deliveries
}

The different options are:

  • 0 logs nothing

  • 1 logs only failed deliveries

  • 2 logs everything (both successful and failed delivery attempts)

Sending Order

The default sending order for emails is -priority, but this can be altered by setting SENDING_ORDER. For example, if you want to send queued emails in FIFO order :

# Put this in settings.py
POST_OFFICE = {
    'SENDING_ORDER': ['created']
}

Context Field Serializer

If you need to store complex Python objects for deferred rendering (i.e. setting render_on_delivery=True), you can specify your own context field class to store context variables. For example if you want to use django-picklefield:

# Put this in settings.py
POST_OFFICE = {
    'CONTEXT_FIELD_CLASS': 'picklefield.fields.PickledObjectField'
}

CONTEXT_FIELD_CLASS defaults to jsonfield.JSONField.

Logging

You can configure post-office’s logging from Django’s settings.py. For example:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        "post_office": {
            "format": "[%(levelname)s]%(asctime)s PID %(process)d: %(message)s",
            "datefmt": "%d-%m-%Y %H:%M:%S",
        },
    },
    "handlers": {
        "post_office": {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "formatter": "post_office"
        },
        # If you use sentry for logging
        'sentry': {
            'level': 'ERROR',
            'class': 'raven.contrib.django.handlers.SentryHandler',
        },
    },
    'loggers': {
        "post_office": {
            "handlers": ["post_office", "sentry"],
            "level": "INFO"
        },
    },
}

Threads

post-office >= 3.0 allows you to use multiple threads to dramatically speed up the speed at which emails are sent. By default, post-office uses 5 threads per process. You can tweak this setting by changing THREADS_PER_PROCESS setting.

This may dramatically increase the speed of bulk email delivery, depending on which email backends you use. In my tests, multi threading speeds up email backends that use HTTP based (REST) delivery mechanisms but doesn’t seem to help SMTP based backends.

# Put this in settings.py
POST_OFFICE = {
    'THREADS_PER_PROCESS': 10
}

Performance

Caching

if Django’s caching mechanism is configured, post_office will cache EmailTemplate instances . If for some reason you want to disable caching, set POST_OFFICE_CACHE to False in settings.py:

## All cache key will be prefixed by post_office:template:
## To turn OFF caching, you need to explicitly set POST_OFFICE_CACHE to False in settings
POST_OFFICE_CACHE = False

## Optional: to use a non default cache backend, add a "post_office" entry in CACHES
CACHES = {
    'post_office': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

send_many()

send_many() is much more performant (generates less database queries) when sending a large number of emails. send_many() is almost identical to mail.send(), with the exception that it accepts a list of keyword arguments that you’d usually pass into mail.send():

from post_office import mail

first_email = {
    'sender': 'from@example.com',
    'recipients': ['alice@example.com'],
    'subject': 'Hi!',
    'message': 'Hi Alice!'
}
second_email = {
    'sender': 'from@example.com',
    'recipients': ['bob@example.com'],
    'subject': 'Hi!',
    'message': 'Hi Bob!'
}
kwargs_list = [first_email, second_email]

mail.send_many(kwargs_list)

Attachments are not supported with mail.send_many().

Running Tests

To run the test suite:

`which django-admin.py` test post_office --settings=post_office.test_settings --pythonpath=.

You can run the full test suite with:

tox

or:

python setup.py test

Changelog

Version 3.0.2

  • _send_bulk now properly catches exceptions when preparing email messages.

Version 3.0.1

  • Fixed an infinite loop bug in send_queued_mail management command.

Version 3.0.0

  • _send_bulk now allows each process to use multiple threads to send emails. Allowing

  • Added support for mimetypes in email attachments. Thanks @clickonchris!

  • An EmailTemplate can now be used as defaults multiple times in one language. Thanks @sac7e!

  • send_queued_mail management command will now check whether there are more queued emails to be sent before exiting.

  • Drop support for Django < 1.8. Thanks @fendyh!

Full changelog can be found here.

Created and maintained by the cool guys at Stamps, Indonesia’s most elegant CRM/loyalty platform.

Metadata

Release files for django-post_office 3.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-post_office 3.0.2
File Size Uploaded
django-post_office-3.0.2.tar.gz 46.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-post_office 3.0.2
File Interpreter ABI Platform
django_post_office-3.0.2-py2.py3-none-any.whl Python 3, Python 2 none any Details

Total release size: 117.4 kB

Release files / django-post_office-3.0.2.tar.gz

Download URL django-post_office-3.0.2.tar.gz
Size 46.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fabf202a04374046792b1a5e0317511130b79ce15621082ef4f0ea6ad9a9ffae
BLAKE2b-256 checksum
How to use checksums
0f88ad81cfa83b736c161d6fb022a99fa035519128a0ddb40145b64c995d6590
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release files / django_post_office-3.0.2-py2.py3-none-any.whl

Download URL django_post_office-3.0.2-py2.py3-none-any.whl
Size 71.1 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
7598d998055e356728879fe4fe5be451a8efbb416900782c9f24438d3c76109c
BLAKE2b-256 checksum
How to use checksums
8d03ce3e85b5252b0bb7df69105ca10398079d748c6846b9c580ee681b27054a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release history Release notifications | RSS feed

3.11.3

2 release files

3.11.2

2 release files

3.11.1

2 release files

3.10.0

2 release files

3.9.1

2 release files

3.9.0

2 release files

3.8.0

2 release files

3.7.1

2 release files

3.7.0

2 release files

3.6.3

2 release files

3.6.2

2 release files

3.6.1

2 release files

3.6.0

2 release files

3.5.3

1 release file

3.5.2

2 release files

3.5.1

1 release file

3.5.0

2 release files

3.4.1

1 release file

3.4.0

1 release file

3.3.1

2 release files

3.3.0

2 release files

3.2.1

1 release file

3.2.0

1 release file

3.1.0

2 release files

3.0.4

2 release files

3.0.3

2 release files

This release

3.0.2 This release

2 release files

3.0.1

2 release files

3.0.0

2 release files

2.0.8

2 release files

2.0.7

2 release files

2.0.6

2 release files

2.0.5

1 release file

2.0.4

1 release file

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.8.4

1 release file

0.8.3

1 release file

0.8.2

1 release file

0.8.1

1 release file

0.8.0

1 release file

0.7.2

1 release file

0.7.1

1 release file

0.7.0

1 release file

0.6.0

1 release file

0.5.2

1 release file

0.5.1

1 release file

0.5.0

1 release file

0.4.0

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.1

1 release file

0.2.0

1 release file

0.1.5

1 release file

0.1.4

1 release file

0.1.3

1 release file

0.1.2

1 release file

0.1.1

1 release file

0.1.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page