133 lines
3.6 KiB
Python
133 lines
3.6 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
|
|
import smtplib
|
|
|
|
import _socket
|
|
|
|
import frappe
|
|
from frappe import _
|
|
from frappe.utils import cint, cstr
|
|
|
|
CONNECTION_FAILED = _("Could not connect to outgoing email server")
|
|
AUTH_ERROR_TITLE = _("Invalid Credentials")
|
|
AUTH_ERROR = _("Incorrect email or password. Please check your login credentials.")
|
|
SOCKET_ERROR_TITLE = _("Incorrect Configuration")
|
|
SOCKET_ERROR = _("Invalid Outgoing Mail Server or Port")
|
|
SEND_MAIL_FAILED = _("Unable to send emails at this time")
|
|
EMAIL_ACCOUNT_MISSING = _(
|
|
"Email Account not setup. Please create a new Email Account from Setup > Email > Email Account"
|
|
)
|
|
|
|
|
|
class InvalidEmailCredentials(frappe.ValidationError):
|
|
pass
|
|
|
|
|
|
def send(email, append_to=None, retry=1):
|
|
"""Deprecated: Send the message or add it to Outbox Email"""
|
|
|
|
def _send(retry):
|
|
from frappe.email.doctype.email_account.email_account import EmailAccount
|
|
|
|
try:
|
|
email_account = EmailAccount.find_outgoing(match_by_doctype=append_to)
|
|
smtpserver = email_account.get_smtp_server()
|
|
|
|
# validate is called in as_string
|
|
email_body = email.as_string()
|
|
|
|
smtpserver.sess.sendmail(email.sender, email.recipients + (email.cc or []), email_body)
|
|
except smtplib.SMTPSenderRefused:
|
|
frappe.throw(_("Invalid login or password"), title="Email Failed")
|
|
raise
|
|
except smtplib.SMTPRecipientsRefused:
|
|
frappe.msgprint(_("Invalid recipient address"), title="Email Failed")
|
|
raise
|
|
except (smtplib.SMTPServerDisconnected, smtplib.SMTPAuthenticationError):
|
|
if not retry:
|
|
raise
|
|
else:
|
|
retry = retry - 1
|
|
_send(retry)
|
|
|
|
_send(retry)
|
|
|
|
|
|
class SMTPServer:
|
|
def __init__(self, server, login=None, password=None, port=None, use_tls=None, use_ssl=None):
|
|
self.login = login
|
|
self.password = password
|
|
self._server = server
|
|
self._port = port
|
|
self.use_tls = use_tls
|
|
self.use_ssl = use_ssl
|
|
self._session = None
|
|
|
|
if not self.server:
|
|
frappe.msgprint(EMAIL_ACCOUNT_MISSING, raise_exception=frappe.OutgoingEmailError)
|
|
|
|
@property
|
|
def port(self):
|
|
port = self._port or (self.use_ssl and 465) or (self.use_tls and 587)
|
|
return cint(port)
|
|
|
|
@property
|
|
def server(self):
|
|
return cstr(self._server or "")
|
|
|
|
def secure_session(self, conn):
|
|
"""Secure the connection incase of TLS."""
|
|
if self.use_tls:
|
|
conn.ehlo()
|
|
conn.starttls()
|
|
conn.ehlo()
|
|
|
|
@property
|
|
def session(self):
|
|
if self.is_session_active():
|
|
return self._session
|
|
|
|
SMTP = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP
|
|
|
|
try:
|
|
_session = SMTP(self.server, self.port)
|
|
if not _session:
|
|
frappe.msgprint(CONNECTION_FAILED, raise_exception=frappe.OutgoingEmailError)
|
|
|
|
self.secure_session(_session)
|
|
if self.login and self.password:
|
|
res = _session.login(str(self.login or ""), str(self.password or ""))
|
|
|
|
# check if logged correctly
|
|
if res[0] != 235:
|
|
frappe.msgprint(res[1], raise_exception=frappe.OutgoingEmailError)
|
|
|
|
self._session = _session
|
|
return self._session
|
|
|
|
except smtplib.SMTPAuthenticationError as e:
|
|
self.throw_invalid_credentials_exception()
|
|
|
|
except _socket.error as e:
|
|
# Invalid mail server -- due to refusing connection
|
|
frappe.throw(SOCKET_ERROR, title=SOCKET_ERROR_TITLE)
|
|
|
|
except smtplib.SMTPException:
|
|
frappe.msgprint(SEND_MAIL_FAILED)
|
|
raise
|
|
|
|
def is_session_active(self):
|
|
if self._session:
|
|
try:
|
|
return self._session.noop()[0] == 250
|
|
except Exception:
|
|
return False
|
|
|
|
def quit(self):
|
|
if self.is_session_active():
|
|
self._session.quit()
|
|
|
|
@classmethod
|
|
def throw_invalid_credentials_exception(cls):
|
|
frappe.throw(AUTH_ERROR, title=AUTH_ERROR_TITLE, exc=InvalidEmailCredentials)
|