django-listmonk-mailer
A Django email backend that sends mail through listmonk's
transactional API, built for
Django 6.1's MAILERS setting.
Once it is configured, send_mail(), EmailMessage.send(), mail_admins(),
password-reset emails and everything else in Django that sends email go out
through listmonk.
Requirements
- Python 3.12+
- Django 6.1+
- A listmonk instance with an API user and a transactional template
Installation
pip install django-listmonk-mailer
Setup
1. Create an API user in listmonk
In listmonk, go to Admin → Users, create an API user and copy its token.
Give it a role that is allowed to send transactional messages (tx:send).
2. Create a transactional template
Go to Campaigns → Templates → New, choose the Transactional type and use this as the body:
{{ if .Tx.Data.html }}{{ Safe .Tx.Data.html }}{{ else }}{{ Safe .Tx.Data.body }}{{ end }}
Set the template's subject to {{ .Tx.Data.subject }} (the backend also sends
the subject as an override, so this is only a fallback). Note the template's ID.
The backend sends this in the template's .Tx.Data:
| Key | Contents |
|---|---|
subject |
The message subject |
body |
The plain-text body (empty for HTML-only messages) |
html |
The HTML body or text/html alternative (empty if none) |
plus anything from the data option and per-message data (see below). You
can also build a fully designed template that uses only your own data keys.
3. Configure MAILERS
# settings.py
MAILERS = {
"default": {
"BACKEND": "django_listmonk_mailer.EmailBackend",
"OPTIONS": {
"url": "https://listmonk.example.com",
"username": "django-api",
"token": env("LISTMONK_TOKEN"),
"template_id": 3,
},
},
}
Then send mail as usual:
from django.core import mail
mail.send_mail("Welcome!", "Thanks for signing up.", None, ["jane@example.com"])
Options
| Option | Default | Description |
|---|---|---|
url |
required | Base URL of your listmonk instance. |
username |
required | listmonk API user name. |
token |
required | listmonk API user token. |
template_id |
required | ID of the transactional template to render messages with. |
subscriber_mode |
"external" |
"external" sends to any address without looking it up; "fallback" uses subscriber data when the address is a subscriber and sends anyway when it isn't; "default" only sends to existing subscribers. None omits the field, for listmonk versions that do not support it. |
messenger |
None |
listmonk messenger to send through (listmonk defaults to email). |
content_type |
None |
"html", "markdown" or "plain". By default it is "html" when the message has an HTML body and "plain" otherwise. |
data |
{} |
Extra values added to .Tx.Data on every message, e.g. {"site_name": "Example"}. |
timeout |
10.0 |
HTTP timeout in seconds. |
verify |
True |
TLS verification, passed to httpx. |
fail_silently |
False |
Ignore listmonk and network errors instead of raising them. |
Multiple configurations
Use named mailers for different templates or listmonk instances, and select
one with using:
MAILERS = {
"default": {
"BACKEND": "django_listmonk_mailer.EmailBackend",
"OPTIONS": {**LISTMONK, "template_id": 3},
},
"receipts": {
"BACKEND": "django_listmonk_mailer.EmailBackend",
"OPTIONS": {**LISTMONK, "template_id": 8},
},
# Error emails that should never raise.
"admin-logging": {
"BACKEND": "django_listmonk_mailer.EmailBackend",
"OPTIONS": {**LISTMONK, "template_id": 3, "fail_silently": True},
},
}
mail.send_mail("Your receipt", body, None, [user.email], using="receipts")
Per-message options
Set a listmonk dict on a message to override any field of the
/api/tx request for that
message. data is merged into the default .Tx.Data rather than replacing it:
from django.core.mail import EmailMessage
message = EmailMessage("Order shipped", "Your order is on its way.", to=[user.email])
message.listmonk = {
"template_id": 12,
"data": {"order_id": order.pk, "tracking_url": order.tracking_url},
}
message.send()
How messages are mapped
- Recipients:
to,ccandbccaddresses are combined and deduplicated intosubscriber_emails. listmonk sends each recipient their own copy, so recipients never see each other's addresses and there is noCcheader. - From:
from_email(orDEFAULT_FROM_EMAIL) is sent asfrom_email. - Bodies: an HTML alternative (
attach_alternative(..., "text/html")) or an HTML body (content_subtype = "html") is sent as.Tx.Data.html. When a message has both plain text and HTML, the plain text is also sent asaltbodyfor a multipart email. - Headers:
reply_toandextra_headersare sent asheaders.From,To,Cc,BccandSubjectheaders are dropped, because listmonk sets those. - Attachments: sent as
multipart/form-datafile uploads.
Errors
Failures raise django_listmonk_mailer.ListmonkError, which has status_code,
response and email_message attributes. It subclasses OSError, like
smtplib.SMTPException, so the
recommended pattern
of catching OSError for delivery problems works unchanged:
try:
mail.send_mail(...)
except OSError:
logger.warning("Could not send email", exc_info=True)
Invalid OPTIONS raise django.core.mail.InvalidMailer when the mailer is
first used.
Development
python -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/pytest
License
MIT
Metadata
Release files for django-listmonk-mailer 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_listmonk_mailer-0.1.0.tar.gz | 9.6 kB | With provenance | Details for django_listmonk_mailer-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_listmonk_mailer-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for django_listmonk_mailer-0.1.0-py3-none-any.whl |
Total release size: 18.1 kB
Release files / django_listmonk_mailer-0.1.0.tar.gz
| Download URL | django_listmonk_mailer-0.1.0.tar.gz |
|---|---|
| Size | 9.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b31a554ce559725121f1cdef8558e61305daf575ef439d8a70dea78df398128b
|
|
BLAKE2b-256 checksum How to use checksums |
aca4bbbfcc9bff45f54bf47e7166e145d1837771248a1487850272c50daee825
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / django_listmonk_mailer-0.1.0-py3-none-any.whl
| Download URL | django_listmonk_mailer-0.1.0-py3-none-any.whl |
|---|---|
| Size | 8.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
11db73ce2ca2b056f775044a982d8ac1f9d0c086317ae02fe27907c68e1a4fa8
|
|
BLAKE2b-256 checksum How to use checksums |
a17e0cd280e87dbeab7466f6bc861e882f008f1ec6f9d47426fc9493eef4a2a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logSecurity for django-listmonk-mailer 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for django_listmonk_mailer-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.