Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

smtp.py 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """SMTP email backend class."""
  2. import smtplib
  3. import socket
  4. import ssl
  5. import threading
  6. from django.conf import settings
  7. from django.core.mail.backends.base import BaseEmailBackend
  8. from django.core.mail.message import sanitize_address
  9. from django.core.mail.utils import DNS_NAME
  10. class EmailBackend(BaseEmailBackend):
  11. """
  12. A wrapper that manages the SMTP network connection.
  13. """
  14. def __init__(self, host=None, port=None, username=None, password=None,
  15. use_tls=None, fail_silently=False, use_ssl=None, timeout=None,
  16. ssl_keyfile=None, ssl_certfile=None,
  17. **kwargs):
  18. super().__init__(fail_silently=fail_silently)
  19. self.host = host or settings.EMAIL_HOST
  20. self.port = port or settings.EMAIL_PORT
  21. self.username = settings.EMAIL_HOST_USER if username is None else username
  22. self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
  23. self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
  24. self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
  25. self.timeout = settings.EMAIL_TIMEOUT if timeout is None else timeout
  26. self.ssl_keyfile = settings.EMAIL_SSL_KEYFILE if ssl_keyfile is None else ssl_keyfile
  27. self.ssl_certfile = settings.EMAIL_SSL_CERTFILE if ssl_certfile is None else ssl_certfile
  28. if self.use_ssl and self.use_tls:
  29. raise ValueError(
  30. "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
  31. "one of those settings to True.")
  32. self.connection = None
  33. self._lock = threading.RLock()
  34. @property
  35. def connection_class(self):
  36. return smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP
  37. def open(self):
  38. """
  39. Ensure an open connection to the email server. Return whether or not a
  40. new connection was required (True or False) or None if an exception
  41. passed silently.
  42. """
  43. if self.connection:
  44. # Nothing to do if the connection is already open.
  45. return False
  46. # If local_hostname is not specified, socket.getfqdn() gets used.
  47. # For performance, we use the cached FQDN for local_hostname.
  48. connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
  49. if self.timeout is not None:
  50. connection_params['timeout'] = self.timeout
  51. if self.use_ssl:
  52. connection_params.update({
  53. 'keyfile': self.ssl_keyfile,
  54. 'certfile': self.ssl_certfile,
  55. })
  56. try:
  57. self.connection = self.connection_class(self.host, self.port, **connection_params)
  58. # TLS/SSL are mutually exclusive, so only attempt TLS over
  59. # non-secure connections.
  60. if not self.use_ssl and self.use_tls:
  61. self.connection.starttls(keyfile=self.ssl_keyfile, certfile=self.ssl_certfile)
  62. if self.username and self.password:
  63. self.connection.login(self.username, self.password)
  64. return True
  65. except (smtplib.SMTPException, socket.error):
  66. if not self.fail_silently:
  67. raise
  68. def close(self):
  69. """Close the connection to the email server."""
  70. if self.connection is None:
  71. return
  72. try:
  73. try:
  74. self.connection.quit()
  75. except (ssl.SSLError, smtplib.SMTPServerDisconnected):
  76. # This happens when calling quit() on a TLS connection
  77. # sometimes, or when the connection was already disconnected
  78. # by the server.
  79. self.connection.close()
  80. except smtplib.SMTPException:
  81. if self.fail_silently:
  82. return
  83. raise
  84. finally:
  85. self.connection = None
  86. def send_messages(self, email_messages):
  87. """
  88. Send one or more EmailMessage objects and return the number of email
  89. messages sent.
  90. """
  91. if not email_messages:
  92. return
  93. with self._lock:
  94. new_conn_created = self.open()
  95. if not self.connection or new_conn_created is None:
  96. # We failed silently on open().
  97. # Trying to send would be pointless.
  98. return
  99. num_sent = 0
  100. for message in email_messages:
  101. sent = self._send(message)
  102. if sent:
  103. num_sent += 1
  104. if new_conn_created:
  105. self.close()
  106. return num_sent
  107. def _send(self, email_message):
  108. """A helper method that does the actual sending."""
  109. if not email_message.recipients():
  110. return False
  111. encoding = email_message.encoding or settings.DEFAULT_CHARSET
  112. from_email = sanitize_address(email_message.from_email, encoding)
  113. recipients = [sanitize_address(addr, encoding) for addr in email_message.recipients()]
  114. message = email_message.message()
  115. try:
  116. self.connection.sendmail(from_email, recipients, message.as_bytes(linesep='\r\n'))
  117. except smtplib.SMTPException:
  118. if not self.fail_silently:
  119. raise
  120. return False
  121. return True