Deprecating or changing how django.core.mail handles fail_silently

Thanks everyone, the discussion has really helped.

New proposal:

  • Django shouldn’t have a default email provider
  • We need a way to send a message when email is configured but avoid an error if it’s not
  • fail_silently is unsalvageable. Deprecate and remove it!

No default email configuration

Django’s current defaults—sending email through an SMTP server on localhost without auth— are not useful in modern infrastructure don’t work with many common server configurations. Your web server[1] probably often isn’t running SMTP. Or if it somehow is, outbound mail almost certainly often requires auth. [Edited in response to Ken’s insights below.]

In a fresh Django project, the current defaults mean trying to send email leads to a cryptic error like “ConnectionRefusedError: [Errno 61] Connection refused.” This comes up often enough that it’s covered in the django-allauth FAQ.

Unlike caches, databases, storages and tasks, there isn’t really a usable email provider to be the default. (We could try something like the console or dummy backends, but I suspect that would be at least as confusing as the current errors.)

I’m going to update the dep to make the default EMAIL_PROVIDERS empty: there is no email provider defined by default. That means trying to send email in a fresh project will raise “InvalidEmailProvider: The email provider ‘default’ doesn’t exist.” (Hopefully no FAQ entry needed​:crossed_fingers:)

(I’m tempted to also add an empty EMAIL_PROVIDERS dict to the settings template, with a comment that enabling email requires a “default” provider.)


“Send email if configured” sending option

Reusable apps need a clean way to say, “I’d like to send some mail, so long as my users have email configured at all.” Django needs it too:[2]

class AdminEmailHandler(logging.Handler):
    def send_mail(self, subject, message, *args, **kwargs):
        if mail.providers.is_configured():
            mail.mail_admins(subject, message, *args, **kwargs)

The new providers.is_configured(using="default") checks if the using alias is defined in EMAIL_PROVIDERS. This approach doesn’t suppress any configuration or sending errors. If you’ve defined a default email provider but have a typo in the host, you want to know about that. Same thing if mail intended for admins is getting dropped because your mail server is unreachable.[3]

[Edit: The original version of this post had a different proposal—meant to accomplish the same thing—here. Check the diffs if you’re interested. Apologies for any confusion.]


Deprecate fail_silently in sending APIs

The semantics of fail_silently=True are unclear and backend dependent. Callers’ intent in using it varies widely—and rarely aligns with its actual behavior. It’s “a language feature (try/except) as an argument” with “worse readability and hidden behavior.”

I’ve seen the light:[4] it’s time to deprecate the fail_silently arg to all sending methods—send_mail(), send_mass_mail(), EmailMessage.send(), mail_admins() and mail_managers().

One nuance: I might keep the current fail_silently implementation inside the smtp EmailBackend, but as configuration only. Someone who needs the “ignore smtp and some OS error” semantics could define an EMAIL_PROVIDERS alias with "fail_silently": True in the OPTIONS. (So long as we deprecate and remove the send-time fail_silently arg, it doesn’t really matter if it’s still a backend option. The dep problem is the mismatch between the two.)


Thoughts?

The first two items would be part of the EMAIL_PROVIDERS dep. We could still deprecate fail_silently ahead of that work.


  1. or development machine ↩︎

  2. I now believe Django’s AdminEmailHandler and BrokenLinkEmailsMiddleware are using fail_silently=True to suppress the cryptic errors caused by Django’s default unconfigured email settings (particularly with AdminEmailHandler, which is enabled by default in production). And not because they want to hide configuration or network errors. But I could be reading it wrong. ↩︎

  3. As a reminder, errors raised from the AdminEmailHandler do not end up back in the AdminEmailHandler—there’s no worry about infinite loops. But they will show up in your server error logs somewhere. ↩︎

  4. The AIs had convinced me removing it entirely would be too disruptive. The humans have convinced me it’s the only pragmatic solution. (Thanks, humans!) ↩︎

3 Likes