smtp email backend class import smtplib import ssl import threading from django conf import settings from django core mail backends base import baseemailbackend from django core mail message import sanitizeaddress from django core mail utils import dnsname from django utils functional import cachedproperty class emailbackendbaseemailbackend def init self hostnone portnone usernamenone passwordnone usetlsnone failsilentlyfalse usesslnone timeoutnone sslkeyfilenone sslcertfilenone kwargs super initfailsilentlyfailsilently self host host or settings emailhost self port port or settings emailport self username settings emailhostuser if username is none else username self password settings emailhostpassword if password is none else password self usetls settings emailusetls if usetls is none else usetls self usessl settings emailusessl if usessl is none else usessl self timeout settings emailtimeout if timeout is none else timeout self sslkeyfile settings emailsslkeyfile if sslkeyfile is none else sslkeyfile self sslcertfile settings emailsslcertfile if sslcertfile is none else sslcertfile if self usessl and self usetls raise valueerror emailusetlsemailusessl are mutually exclusive so only set one of those settings to true self connection none self lock threading rlock property def connectionclassself return smtplib smtpssl if self usessl else smtplib smtp cachedproperty def sslcontextself if self sslcertfile or self sslkeyfile sslcontext ssl sslcontextprotocolssl protocoltlsclient sslcontext loadcertchainself sslcertfile self sslkeyfile return sslcontext else return ssl createdefaultcontext def openself if self connection nothing to do if the connection is already open return false if localhostname is not specified socket getfqdn gets used for performance we use the cached fqdn for localhostname connectionparams localhostname dnsname getfqdn if self timeout is not none connectionparamstimeout self timeout if self usessl connectionparamscontext self sslcontext try self connection self connectionclass self host self port connectionparams tlsssl are mutually exclusive so only attempt tls over nonsecure connections if not self usessl and self usetls self connection starttlscontextself sslcontext if self username and self password self connection loginself username self password return true except oserror if not self failsilently raise def closeself this happens when calling quit on a tls connection sometimes or when the connection was already disconnected by the server send one or more emailmessage objects and return the number of email messages sent we failed silently on open trying to send would be pointless a helper method that does the actual sending if not emailmessage recipients return false encoding emailmessage encoding or settings defaultcharset fromemail sanitizeaddressemailmessage fromemail encoding recipients sanitizeaddressaddr encoding for addr in emailmessage recipients message emailmessage message try self connection sendmail fromemail recipients message asbyteslineseprn except smtplib smtpexception if not self failsilently raise return false return true a wrapper that manages the smtp network connection ensure an open connection to the email server return whether or not a new connection was required true or false or none if an exception passed silently nothing to do if the connection is already open if local_hostname is not specified socket getfqdn gets used for performance we use the cached fqdn for local_hostname tls ssl are mutually exclusive so only attempt tls over non secure connections close the connection to the email server this happens when calling quit on a tls connection sometimes or when the connection was already disconnected by the server send one or more emailmessage objects and return the number of email messages sent we failed silently on open trying to send would be pointless a helper method that does the actual sending
import smtplib
import ssl
import threading

from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend
from django.core.mail.message import sanitize_address
from django.core.mail.utils import DNS_NAME
from django.utils.functional import cached_property


class EmailBackend(BaseEmailBackend):
    

    def __init__(
        self,
        host=None,
        port=None,
        username=None,
        password=None,
        use_tls=None,
        fail_silently=False,
        use_ssl=None,
        timeout=None,
        ssl_keyfile=None,
        ssl_certfile=None,
        **kwargs,
    ):
        super().__init__(fail_silently=fail_silently)
        self.host = host or settings.EMAIL_HOST
        self.port = port or settings.EMAIL_PORT
        self.username = settings.EMAIL_HOST_USER if username is None else username
        self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
        self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
        self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
        self.timeout = settings.EMAIL_TIMEOUT if timeout is None else timeout
        self.ssl_keyfile = (
            settings.EMAIL_SSL_KEYFILE if ssl_keyfile is None else ssl_keyfile
        )
        self.ssl_certfile = (
            settings.EMAIL_SSL_CERTFILE if ssl_certfile is None else ssl_certfile
        )
        if self.use_ssl and self.use_tls:
            raise ValueError(
                "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
                "one of those settings to True."
            )
        self.connection = None
        self._lock = threading.RLock()

    @property
    def connection_class(self):
        return smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP

    @cached_property
    def ssl_context(self):
        if self.ssl_certfile or self.ssl_keyfile:
            ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
            ssl_context.load_cert_chain(self.ssl_certfile, self.ssl_keyfile)
            return ssl_context
        else:
            return ssl.create_default_context()

    def open(self):
        
        if self.connection:
            
            return False

        
        
        connection_params = {"local_hostname": DNS_NAME.get_fqdn()}
        if self.timeout is not None:
            connection_params["timeout"] = self.timeout
        if self.use_ssl:
            connection_params["context"] = self.ssl_context
        try:
            self.connection = self.connection_class(
                self.host, self.port, **connection_params
            )

            
            
            if not self.use_ssl and self.use_tls:
                self.connection.starttls(context=self.ssl_context)
            if self.username and self.password:
                self.connection.login(self.username, self.password)
            return True
        except OSError:
            if not self.fail_silently:
                raise

    def close(self):
        
        if self.connection is None:
            return
        try:
            try:
                self.connection.quit()
            except (ssl.SSLError, smtplib.SMTPServerDisconnected):
                
                
                
                self.connection.close()
            except smtplib.SMTPException:
                if self.fail_silently:
                    return
                raise
        finally:
            self.connection = None

    def send_messages(self, email_messages):
        
        if not email_messages:
            return 0
        with self._lock:
            new_conn_created = self.open()
            if not self.connection or new_conn_created is None:
                
                
                return 0
            num_sent = 0
            try:
                for message in email_messages:
                    sent = self._send(message)
                    if sent:
                        num_sent += 1
            finally:
                if new_conn_created:
                    self.close()
        return num_sent

    def _send(self, email_message):
        
        if not email_message.recipients():
            return False
        encoding = email_message.encoding or settings.DEFAULT_CHARSET
        from_email = sanitize_address(email_message.from_email, encoding)
        recipients = [
            sanitize_address(addr, encoding) for addr in email_message.recipients()
        ]
        message = email_message.message()
        try:
            self.connection.sendmail(
                from_email, recipients, message.as_bytes(linesep="\r\n")
            )
        except smtplib.SMTPException:
            if not self.fail_silently:
                raise
            return False
        return True