# This file is part of Pimlico
# Copyright (C) 2020 Mark Granroth-Wilding
# Licensed under the GNU LGPL v3.0 - https://www.gnu.org/licenses/lgpl-3.0.en.html
"""Email sending utilities
Configure email sending functionality by adding the following fields to your Pimlico local config file:
`email_sender`
From-address for all sent emails
`email_recipients`
To-addresses, separated by commas. All notification emails will be sent to all recipients
`email_host`
(optional) Hostname of your SMTP server. Defaults to `localhost`
`email_username`
(optional) Username to authenticate with your SMTP server. If not given, it is assumed that no authentication
is required
`email_password`
(optional) Password to authenticate with your SMTP server. Must be supplied if `username` is given
"""
from __future__ import absolute_import
from builtins import str
from past.builtins import basestring
from builtins import object
import smtplib
from email.mime.text import MIMEText
from smtplib import SMTPHeloError, SMTPAuthenticationError, SMTPException
[docs]class EmailConfig(object):
def __init__(self, sender=None, recipients=None, host=None, username=None, password=None):
self.password = password
self.username = username
self.host = host
if isinstance(recipients, basestring):
recipients = [recipients]
self.recipients = recipients
self.sender = sender
if username is not None and password is None:
raise EmailError("username was supplied for SMTP, but no password")
[docs] @classmethod
def from_local_config(cls, local_config):
# Expect to find a list of recipients in the local config
if "email_recipients" not in local_config:
raise EmailError("no recipient(s) specified in the local config. You should "
"set 'email_recipients' field in your local config file")
recipients = local_config["email_recipients"].split(",")
if "email_sender" not in local_config:
raise EmailError("no sender specified in the local config. You should "
"set 'email_sender' field in your local config file")
sender = local_config["email_sender"]
# Fall back to default of localhost
host = local_config.get("email_host", "localhost")
# Allow a username to be supplied
# Otherwise, we assume no authentication is needed
username = local_config.get("email_username", None)
# Same with the password
password = local_config.get("email_password", None)
return EmailConfig(sender=sender, recipients=recipients, host=host, username=username, password=password)
[docs]def send_pimlico_email(subject, content, local_config, log):
"""
Primary method for sending emails from Pimlico. Tries to send an email with the given content, using
the email details found in the local config. If something goes wrong, an error is logged on the given
log.
:param subject: email subject
:param content: email text (may be unicode)
:param local_config: local config dictionary
:param log: logger to log errors to (and info if the sending works)
"""
try:
# Read email config from the local config
config = EmailConfig.from_local_config(local_config)
data = send_text_email(config, subject, content=content)
except Exception as e:
log.error("Could not send email: %s" % e)
return {"success": False}
else:
log.info("Send email to %s" % ", ".join(data["recipients"]))
data["success"] = True
return data
[docs]def send_text_email(email_config, subject, content=None):
# Encode unicode content as utf-8
if content is None:
content = ""
body = str(content).encode("utf-8")
# Create a plain message
msg = MIMEText(body)
# Allow a single recipient to be given
if email_config.recipients is None:
raise EmailError("email recipient(s) must be specified")
if email_config.sender is None:
raise EmailError("email sender must be specified")
# Set the important headers
msg['Subject'] = subject
msg['From'] = email_config.sender
msg['To'] = ", ".join(email_config.recipients)
# Send the email, via the SMTP server
s = smtplib.SMTP(email_config.host)
if email_config.username is not None:
# Login details have been supplied, so authenticate with SMTP server
try:
s.login(email_config.username, email_config.password)
except SMTPHeloError as e:
raise EmailError("invalid HELO response from SMTP server: %s" % e)
except SMTPAuthenticationError as e:
raise EmailError("could not authenticate with SMTP server (host '%s' using username '%s'): %s" % (
email_config.host, email_config.username, e
))
except SMTPException as e:
raise EmailError("could not find a suitable SMTP authentication method: %s" % e)
# Send the message
email_content = msg.as_string()
s.sendmail(email_config.sender, email_config.recipients, email_content)
s.quit()
# Return the sending details we used
return {
"subject": subject,
"recipients": email_config.recipients,
"sender": email_config.sender,
"username": email_config.username,
"content": email_content,
}
[docs]class EmailError(Exception):
pass