Sasha
Sasha

Reputation: 367

Email django server errors from different smtp server

There are SMTP Sendgrid settings in my settings.py file for user-notification purposes:

EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'my_username'
EMAIL_HOST_PASSWORD = 'my_password'
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = 'my_from_email'

It works good, but the problem is that I want to use different SMTP server for the built-in Internal Server Errors notification system, which is auto-enabled when DEBUG = False and ADMINS tuple is not empty.

How could it be achieved?

Thanks in advance

Upvotes: 2

Views: 1514

Answers (3)

Mark
Mark

Reputation: 1295

Tweaking @dani-herrera's answer, this worked for me in Django 1.11: modify the connection property instead of the __init__.

All together, the full code is as follows.

In settings.py:

# For transactional emails:
SENDGRID_API_KEY = "SG.1234ABCD"
EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_HOST_USER='apikey'
EMAIL_HOST_PASSWORD=SENDGRID_API_KEY
# ... ^that's used for all emails EXCEPT server error alerts.

# Change one line in LOGGING, to use custom awesomeAdminEmailHandler for server emails:
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': { 'class': 'logging.StreamHandler' },
        'mail_admins': {
                        'level': 'ERROR',
                        # remove this default: 'class': 'django.utils.log.AdminEmailHandler',
                        # replace with custom smtp login for ERROR emails:
                        'class': 'myapp.email.adminsender.awesomeAdminEmailHandler', # yes, in quotes
                        # 'include_html': False, # The include_html argument of AdminEmailHandler is used to control whether the traceback email includes an HTML attachment containing the full content of the debug Web page that would have been produced if DEBUG were True.  This information is potentially very sensitive, and you may not want to send it over email.
                     }
    },
    'loggers': {
        # [Don't change anything]
        ...
        ...
    }
}

Then in myapp/email/adminsender.py file:

from django.utils.log import AdminEmailHandler
from django.core.mail import get_connection

class awesomeAdminEmailHandler( AdminEmailHandler ):
    """
        In this example, we create a special Gmail account just for sending error emails.
        Code inspired by https://stackoverflow.com/a/33010978/870121
    """
    def connection(self):
        my_host = "smtp.gmail.com"
        my_port = 587
        my_username = "[email protected]"
        my_password = "abcd1234"
        my_use_tls = True
        return get_connection(backend=self.email_backend,
                              host=my_host,
                              port=my_port,
                              username=my_username,
                              password=my_password,
                              use_tls=my_use_tls)

Motivation for bothering to do this:
If we send ALL emails through Sendgrid, i.e., transactional app (user) emails and server error emails, that's not ideal.
One server error might cause ~100s of emails to be sent in 2 minutes.
Most of these emails won't be opened.
This low open rate might make your dedicated IP address look bad (if you have one), and/or hurt the reputation of our sending domain (myapp.com) too? Or, you might find it uses up your monthly limit of # of emails sent. So, preserve 'myapp.com' for important user-facing emails, and use this awesomeAdminEmailHandler for sending 5xx error emails from a different email account.

Note on Gmail as in this example
Gmail is a great idea for testing, but probably not a good idea for use in production. SMTP will work as in the code snippet above iff you first allow "insecure apps", https://myaccount.google.com/lesssecureapps

Upvotes: 2

Mohamed ElKalioby
Mohamed ElKalioby

Reputation: 2334

The code isn't 100% accurate with Django 1.8

from django.utils.log import AdminEmailHandler
from django.core.mail import get_connection
from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend
from django.core.mail.message import EmailMultiAlternatives

class SMTPConnection (EmailBackend):
    def __call__(self, *args, **kwargs):
       self.host = settings.ADMINS_SMTP["HOST"]
       self.port = settings.ADMINS_SMTP["PORT"]
       self.username = settings.ADMINS_SMTP["USERNAME"]
       self.password = settings.ADMINS_SMTP["PASSWORD"]
       self.use_tls = settings.ADMINS_SMTP["USE_TLS"]
       return self

class EmailAdmins( AdminEmailHandler ):
    def __init__(self, include_html=False, email_backend=None):
       self.include_html=include_html
       AdminEmailHandler.__init__(self, include_html, email_backend)
       self.connection =     SMTPConnection(host=settings.ADMINS_SMTP["HOST"],port=settings.ADMINS_SMTP["PORT"],username=settings.ADMINS_SMTP["USERNAME"],password=settings.ADMINS_SMTP["PASSWORD"],use_tls=settings.ADMINS_SMTP["USE_TLS"])

def send_mail(self, subject, message, *args, **kwargs):
    if not settings.ADMINS:
        return
    mail = EmailMultiAlternatives(subject='%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject),
                                  body=message, from_email=settings.ADMINS_SMTP["USERNAME"], to=[a[1] for a in settings.ADMINS],
                                  connection=self.connection,)
    mail.send(fail_silently=True)

Upvotes: 2

dani herrera
dani herrera

Reputation: 51655

You can set a different error handler on settings:

'handlers': {
    'null': {
        'level': 'DEBUG',
        'class': 'logging.NullHandler',
    },
    'console': {
        'level': 'DEBUG',
        'class': 'logging.StreamHandler',
        'formatter': 'simple'
    },
    'mail_admins': {
        'level': 'ERROR',
        'class': 'myAdminEmailHandler',
        'filters': ['special']
    }

And overwrite connection there:

from django.utils.log import AdminEmailHandler
from django.core.mail import get_connection


class myAdminEmailHandler( AdminEmailHandler ):

    def __init__(self, include_html=False, email_backend=None):
        AdminEmailHandler.__init__(self,include_html, email_backend)

        self.my_host = ''
        self.my_port = 587
        self.my_username = ''
        self.my_password = ''
        self.my_use_tls = True
        self.connection = get_connection(host=my_host, 
                                    port=my_port, 
                                    username=my_username, 
                                    password=my_password, 
                                    use_tls=my_use_tls)

Disclaimer, not tested. Some credits to @Daniel Backman: https://stackoverflow.com/a/14398138

Upvotes: 2

Related Questions