Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion django/core/checks/mail.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.conf import settings
from django.core.checks import Tags, Warning, register
from django.core.checks import Error, Tags, Warning, register
from django.core.mail import DEFAULT_MAILER_ALIAS


Expand Down Expand Up @@ -27,3 +27,37 @@ def check_mailers_default_alias(app_configs, **kwargs):
id="mail.W001",
)
]


NON_PRODUCTION_EMAIL_BACKENDS = {
"django.core.mail.backends.console.EmailBackend",
"django.core.mail.backends.dummy.EmailBackend",
"django.core.mail.backends.filebased.EmailBackend",
"django.core.mail.backends.locmem.EmailBackend",
}


@register(Tags.mail, deploy=True)
def check_mailers_production_backend(app_configs, **kwargs):
try:
backend = settings.MAILERS[DEFAULT_MAILER_ALIAS]["BACKEND"]
except (AttributeError, KeyError):
# There is no "default" backend to inspect: either MAILERS is not
# defined, or there is no "default" entry, or no "BACKEND" key in it.
# An omitted BACKEND defaults to SMTP, which is fine.
return []

if backend not in NON_PRODUCTION_EMAIL_BACKENDS:
return []

return [
Error(
f"Your MAILERS setting uses a development-only email backend in the "
f"'{DEFAULT_MAILER_ALIAS}' entry ({backend}).",
hint=(
"Use a production-ready email backend, such as the SMTP backend, "
"otherwise email will not be sent."
),
id="mail.E001",
)
]
3 changes: 3 additions & 0 deletions docs/ref/checks.txt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ Mail
The following checks verify that your :setting:`MAILERS` setting is correctly
configured:

* **mail.E001**: Your :setting:`MAILERS` setting uses a development-only email
backend in the ``'default'`` entry (``<backend>``). (Only checked when the
:option:`check --deploy` option is used.)
* **mail.W001**: Your :setting:`MAILERS` setting has no ``'default'`` entry.
Sending email without a valid mailer will fail.

Expand Down
4 changes: 4 additions & 0 deletions docs/releases/6.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ CSP
Email
~~~~~

* A new ``mail.E001`` deployment-only system check prevents using one of
Django's email backends that is not intended for production use in the
``'default'`` :setting:`MAILERS` entry.

* A new ``mail.W001`` system check warns when :setting:`MAILERS` is defined but
does not include a ``'default'`` entry.

Expand Down
64 changes: 62 additions & 2 deletions tests/check_framework/test_mail.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.core.checks import Warning
from django.core.checks.mail import check_mailers_default_alias
from django.core.checks import Error, Warning, registry
from django.core.checks.mail import (
check_mailers_default_alias,
check_mailers_production_backend,
)
from django.test import SimpleTestCase, override_settings


Expand Down Expand Up @@ -53,3 +56,60 @@ def test_mailers_without_default_alias(self):
)
],
)


class MailersProductionBackendTests(SimpleTestCase):
def test_is_deployment_only(self):
self.assertIn(
check_mailers_production_backend, registry.registry.deployment_checks
)
self.assertNotIn(
check_mailers_production_backend, registry.registry.registered_checks
)

def test_mailers_not_defined(self):
self.assertEqual(check_mailers_production_backend(None), [])

def test_production_backends(self):
production_backends = [
"django.core.mail.backends.smtp.EmailBackend",
"any.third.party.EmailBackend",
]
for backend in production_backends:
with (
self.subTest(backend=backend),
self.settings(MAILERS={"default": {"BACKEND": backend}}),
):
self.assertEqual(check_mailers_production_backend(None), [])

def test_non_production_backends(self):
hint = (
"Use a production-ready email backend, such as the SMTP backend, "
"otherwise email will not be sent."
)
for backend_type in ["console", "dummy", "filebased", "locmem"]:
backend = f"django.core.mail.backends.{backend_type}.EmailBackend"
msg = (
f"Your MAILERS setting uses a development-only email backend in "
f"the 'default' entry ({backend})."
)
with (
self.subTest(backend=backend),
self.settings(MAILERS={"default": {"BACKEND": backend}}),
):
self.assertEqual(
check_mailers_production_backend(None),
[Error(msg, hint=hint, id="mail.E001")],
)

@override_settings(
MAILERS={
"default": {"BACKEND": "django.core.mail.backends.smtp.EmailBackend"},
"secondary": {"BACKEND": "django.core.mail.backends.console.EmailBackend"},
}
)
def test_only_applies_to_default_mailer(self):
self.assertEqual(
check_mailers_production_backend(None),
[],
)
Loading