Source code for sirepo.smtp
"""SMTP connection to send emails
:copyright: Copyright (c) 2018-2024 RadiaSoft LLC. All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""
from pykern.pkdebug import pkdp, pkdlog
from pykern import pkconfig
from pykern.pkcollections import PKDict
import email
import email.utils
import pyisemail
import smtplib
_DEV_SMTP_SERVER = "dev"
_SEND = None
_FROM_DOMAIN = None
_cfg = None
[docs]
def send(recipient, subject, body):
if _cfg.server == _DEV_SMTP_SERVER:
pkdlog("DEV configuration so not sending to {}", recipient)
return False
m = email.message.EmailMessage()
m["From"] = f"{_cfg.from_name} <{_cfg.from_email}>"
m["To"] = recipient
m["Subject"] = subject
m["Message-Id"] = email.utils.make_msgid(domain=_FROM_DOMAIN)
m.set_content(body)
_SEND(m)
return True
def _cfg_from_email(value):
if not pyisemail.is_email(value):
pkconfig.raise_error(f"invalid from_email={value}")
return value.lower()
def _mx(msg):
import dns.resolver
h = msg["To"].split("@")[1]
try:
for x in sorted(
dns.resolver.resolve(h, "MX"),
key=lambda x: x.preference,
):
yield str(x.exchange)
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
yield h
def _send_directly(msg):
for h in _mx(msg):
# Port 465 is not an official IETF port for SMTPS and has been
# reassigned so always try port 25 with starttls. stmplib will try
# 465 if it can import ssl even if the remote server doesn't support it.
with smtplib.SMTP(h, 25) as s:
try:
s.starttls()
except smtplib.SMTPNotSupportedError:
pass
s.ehlo()
s.send_message(msg)
def _send_via_relay_server(msg):
with smtplib.SMTP(_cfg.server, _cfg.port) as s:
s.starttls()
s.ehlo()
if _cfg.user and _cfg.password:
s.login(_cfg.user, _cfg.password)
s.send_message(msg)
def _init():
global _cfg, _SEND, _FROM_DOMAIN
if _cfg:
return
_cfg = pkconfig.init(
from_email=("support@sirepo.com", _cfg_from_email, "Email address of sender"),
from_name=("Sirepo Support", str, "Name of email sender"),
password=(None, str, "SMTP password"),
port=(587, int, "SMTP Port"),
send_directly=(False, bool, "Send directly to the server in the recipient"),
server=(None, str, "SMTP TLS server"),
user=(None, str, "SMTP user"),
)
_FROM_DOMAIN = _cfg.from_email.split("@")[1]
if _cfg.send_directly:
_cfg.server = "not " + _DEV_SMTP_SERVER
_SEND = _send_directly
return
_SEND = _send_via_relay_server
if pkconfig.in_dev_mode():
if _cfg.server is None:
_cfg.server = _DEV_SMTP_SERVER
return
if _cfg.server is None:
pkconfig.raise_error(
f"server={_cfg.server} must be defined",
)
if bool(_cfg.user) != bool(_cfg.password):
pkconfig.raise_error(
f"user={_cfg.user} and password={_cfg.password} must be both set or not"
)
_init()