core/homeassistant/components/notify/smtp.py

186 lines
5.8 KiB
Python
Raw Normal View History

2015-06-03 18:39:16 +00:00
"""
Mail (SMTP) notification service.
2015-06-03 18:39:16 +00:00
2015-10-13 20:56:12 +00:00
For more details about this platform, please refer to the documentation at
2015-11-09 17:33:11 +00:00
https://home-assistant.io/components/notify.smtp/
2015-06-03 18:39:16 +00:00
"""
import logging
import smtplib
from email.mime.multipart import MIMEMultipart
2015-06-03 18:39:16 +00:00
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
2015-06-03 18:39:16 +00:00
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
2015-06-03 18:39:16 +00:00
from homeassistant.components.notify import (
ATTR_TITLE, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService)
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
2015-06-03 18:39:16 +00:00
_LOGGER = logging.getLogger(__name__)
ATTR_IMAGES = 'images' # optional embedded image file attachments
CONF_STARTTLS = 'starttls'
CONF_SENDER = 'sender'
CONF_RECIPIENT = 'recipient'
CONF_DEBUG = 'debug'
CONF_SERVER = 'server'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RECIPIENT): vol.Email,
vol.Optional(CONF_SERVER, default='localhost'): cv.string,
vol.Optional(CONF_PORT, default=25): cv.port,
vol.Optional(CONF_SENDER): vol.Email,
vol.Optional(CONF_STARTTLS, default=False): cv.boolean,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_DEBUG, default=False): cv.boolean,
})
2015-06-03 18:39:16 +00:00
def get_service(hass, config):
2016-03-08 10:46:32 +00:00
"""Get the mail notification service."""
mail_service = MailNotificationService(
config.get(CONF_SERVER),
config.get(CONF_PORT),
config.get(CONF_SENDER),
config.get(CONF_STARTTLS),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
config.get(CONF_RECIPIENT),
config.get(CONF_DEBUG))
if mail_service.connection_is_valid():
return mail_service
else:
2015-06-03 18:39:16 +00:00
return None
# pylint: disable=too-few-public-methods, too-many-instance-attributes
class MailNotificationService(BaseNotificationService):
2016-03-08 10:46:32 +00:00
"""Implement the notification service for E-Mail messages."""
2015-06-03 18:39:16 +00:00
# pylint: disable=too-many-arguments
def __init__(self, server, port, sender, starttls, username,
password, recipient, debug):
2016-03-08 10:46:32 +00:00
"""Initialize the service."""
2015-06-03 18:39:16 +00:00
self._server = server
self._port = port
self._sender = sender
2015-11-09 06:15:34 +00:00
self.starttls = starttls
2015-06-03 18:39:16 +00:00
self.username = username
self.password = password
self.recipient = recipient
self.debug = debug
self.tries = 2
def connect(self):
2016-03-08 10:46:32 +00:00
"""Connect/authenticate to SMTP Server."""
mail = smtplib.SMTP(self._server, self._port, timeout=5)
mail.set_debuglevel(self.debug)
mail.ehlo_or_helo_if_needed()
if self.starttls:
mail.starttls()
mail.ehlo()
if self.username and self.password:
mail.login(self.username, self.password)
return mail
2015-06-03 18:39:16 +00:00
def connection_is_valid(self):
"""Check for valid config, verify connectivity."""
server = None
try:
server = self.connect()
except smtplib.socket.gaierror:
_LOGGER.exception(
"SMTP server not found (%s:%s). "
"Please check the IP address or hostname of your SMTP server.",
self._server, self._port)
return False
except (smtplib.SMTPAuthenticationError, ConnectionRefusedError):
_LOGGER.exception(
"Login not possible. "
"Please check your setting and/or your credentials.")
return False
finally:
if server:
server.quit()
return True
2015-06-03 18:39:16 +00:00
def send_message(self, message="", **kwargs):
"""
Build and send a message to a user.
Will send plain text normally, or will build a multipart HTML message
with inline image attachments if images config is defined.
"""
2015-06-03 18:39:16 +00:00
subject = kwargs.get(ATTR_TITLE)
data = kwargs.get(ATTR_DATA)
if data:
msg = _build_multipart_msg(message, images=data.get(ATTR_IMAGES))
else:
msg = _build_text_msg(message)
2015-06-03 18:39:16 +00:00
msg['Subject'] = subject
msg['To'] = self.recipient
msg['From'] = self._sender
msg['X-Mailer'] = 'HomeAssistant'
return self._send_email(msg)
def _send_email(self, msg):
"""Send the message."""
mail = self.connect()
2015-09-24 15:47:19 +00:00
for _ in range(self.tries):
try:
mail.sendmail(self._sender, self.recipient,
msg.as_string())
break
2015-09-24 15:55:24 +00:00
except smtplib.SMTPException:
2015-09-24 16:20:25 +00:00
_LOGGER.warning('SMTPException sending mail: '
'retrying connection')
mail.quit()
mail = self.connect()
mail.quit()
def _build_text_msg(message):
"""Build plaintext email."""
_LOGGER.debug('Building plain text email.')
return MIMEText(message)
def _build_multipart_msg(message, images):
"""Build Multipart message with in-line images."""
_LOGGER.debug('Building multipart email with embedded attachment(s).')
msg = MIMEMultipart('related')
msg_alt = MIMEMultipart('alternative')
msg.attach(msg_alt)
body_txt = MIMEText(message)
msg_alt.attach(body_txt)
body_text = ['<p>{}</p><br>'.format(message)]
for atch_num, atch_name in enumerate(images):
cid = 'image{}'.format(atch_num)
body_text.append('<img src="cid:{}"><br>'.format(cid))
try:
with open(atch_name, 'rb') as attachment_file:
attachment = MIMEImage(attachment_file.read())
msg.attach(attachment)
attachment.add_header('Content-ID', '<{}>'.format(cid))
except FileNotFoundError:
_LOGGER.warning('Attachment %s not found. Skipping.',
atch_name)
body_html = MIMEText(''.join(body_text), 'html')
msg_alt.attach(body_html)
return msg