mirror of
https://github.com/home-assistant/core.git
synced 2025-04-22 16:27:56 +00:00
SMTP notify enhancements: full HTML emails and custom product_name
in email headers (#7533)
* SMTP notify enhancements: HTML emails and customization options - Send full HTML emails, with or without inline attached images. - Custom `timeout`. - Custom `product_name` identifier for the `FROM` and `X-MAILER` email headers. - New HTML email test * `sender_name` instead of product_name - Change `sender_name` instead of `product_name`. - No changes in `X-Mailer` header. - `From` header as always unless you define the new `sender_name` parameter.
This commit is contained in:
parent
36d7fe72eb
commit
d0304198de
@ -9,9 +9,9 @@ import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.image import MIMEImage
|
||||
import email.utils
|
||||
from email.mime.application import MIMEApplication
|
||||
|
||||
import email.utils
|
||||
import os
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.notify import (
|
||||
@ -26,10 +26,12 @@ import homeassistant.util.dt as dt_util
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_IMAGES = 'images' # optional embedded image file attachments
|
||||
ATTR_HTML = 'html'
|
||||
|
||||
CONF_STARTTLS = 'starttls'
|
||||
CONF_DEBUG = 'debug'
|
||||
CONF_SERVER = 'server'
|
||||
CONF_SENDER_NAME = 'sender_name'
|
||||
|
||||
DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_PORT = 25
|
||||
@ -47,6 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_STARTTLS, default=DEFAULT_STARTTLS): cv.boolean,
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_SENDER_NAME): cv.string,
|
||||
vol.Optional(CONF_DEBUG, default=DEFAULT_DEBUG): cv.boolean,
|
||||
})
|
||||
|
||||
@ -62,6 +65,7 @@ def get_service(hass, config, discovery_info=None):
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD),
|
||||
config.get(CONF_RECIPIENT),
|
||||
config.get(CONF_SENDER_NAME),
|
||||
config.get(CONF_DEBUG))
|
||||
|
||||
if mail_service.connection_is_valid():
|
||||
@ -74,7 +78,7 @@ class MailNotificationService(BaseNotificationService):
|
||||
"""Implement the notification service for E-Mail messages."""
|
||||
|
||||
def __init__(self, server, port, timeout, sender, starttls, username,
|
||||
password, recipients, debug):
|
||||
password, recipients, sender_name, debug):
|
||||
"""Initialize the service."""
|
||||
self._server = server
|
||||
self._port = port
|
||||
@ -84,6 +88,8 @@ class MailNotificationService(BaseNotificationService):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.recipients = recipients
|
||||
self._sender_name = sender_name
|
||||
self._timeout = timeout
|
||||
self.debug = debug
|
||||
self.tries = 2
|
||||
|
||||
@ -128,19 +134,28 @@ class MailNotificationService(BaseNotificationService):
|
||||
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.
|
||||
with inline image attachments if images config is defined, or will
|
||||
build a multipart HTML if html config is defined.
|
||||
"""
|
||||
subject = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
|
||||
data = kwargs.get(ATTR_DATA)
|
||||
|
||||
if data:
|
||||
msg = _build_multipart_msg(message, images=data.get(ATTR_IMAGES))
|
||||
if ATTR_HTML in data:
|
||||
msg = _build_html_msg(message, data[ATTR_HTML],
|
||||
images=data.get(ATTR_IMAGES))
|
||||
else:
|
||||
msg = _build_multipart_msg(message,
|
||||
images=data.get(ATTR_IMAGES))
|
||||
else:
|
||||
msg = _build_text_msg(message)
|
||||
|
||||
msg['Subject'] = subject
|
||||
msg['To'] = ','.join(self.recipients)
|
||||
msg['From'] = self._sender
|
||||
if self._sender_name:
|
||||
msg['From'] = '{} <{}>'.format(self._sender_name, self._sender)
|
||||
else:
|
||||
msg['From'] = self._sender
|
||||
msg['X-Mailer'] = 'HomeAssistant'
|
||||
msg['Date'] = email.utils.format_datetime(dt_util.now())
|
||||
msg['Message-Id'] = email.utils.make_msgid()
|
||||
@ -155,12 +170,16 @@ class MailNotificationService(BaseNotificationService):
|
||||
mail.sendmail(self._sender, self.recipients,
|
||||
msg.as_string())
|
||||
break
|
||||
except smtplib.SMTPServerDisconnected:
|
||||
_LOGGER.warning(
|
||||
"SMTPServerDisconnected sending mail: retrying connection")
|
||||
mail.quit()
|
||||
mail = self.connect()
|
||||
except smtplib.SMTPException:
|
||||
_LOGGER.warning(
|
||||
"SMTPException sending mail: retrying connection")
|
||||
mail.quit()
|
||||
mail = self.connect()
|
||||
|
||||
mail.quit()
|
||||
|
||||
|
||||
@ -204,3 +223,25 @@ def _build_multipart_msg(message, images):
|
||||
body_html = MIMEText(''.join(body_text), 'html')
|
||||
msg_alt.attach(body_html)
|
||||
return msg
|
||||
|
||||
|
||||
def _build_html_msg(text, html, images):
|
||||
"""Build Multipart message with in-line images and rich html (UTF-8)."""
|
||||
_LOGGER.debug("Building html rich email")
|
||||
msg = MIMEMultipart('related')
|
||||
alternative = MIMEMultipart('alternative')
|
||||
alternative.attach(MIMEText(text, _charset='utf-8'))
|
||||
alternative.attach(MIMEText(html, ATTR_HTML, _charset='utf-8'))
|
||||
msg.attach(alternative)
|
||||
|
||||
for atch_num, atch_name in enumerate(images):
|
||||
name = os.path.basename(atch_name)
|
||||
try:
|
||||
with open(atch_name, 'rb') as attachment_file:
|
||||
attachment = MIMEImage(attachment_file.read(), filename=name)
|
||||
msg.attach(attachment)
|
||||
attachment.add_header('Content-ID', '<{}>'.format(name))
|
||||
except FileNotFoundError:
|
||||
_LOGGER.warning('Attachment %s [#%s] not found. Skipping',
|
||||
atch_name, atch_num)
|
||||
return msg
|
||||
|
@ -23,7 +23,8 @@ class TestNotifySmtp(unittest.TestCase):
|
||||
self.hass = get_test_home_assistant()
|
||||
self.mailer = MockSMTP('localhost', 25, 5, 'test@test.com', 1,
|
||||
'testuser', 'testpass',
|
||||
['recip1@example.com', 'testrecip@test.com'], 0)
|
||||
['recip1@example.com', 'testrecip@test.com'],
|
||||
'HomeAssistant', 0)
|
||||
|
||||
def tearDown(self): # pylint: disable=invalid-name
|
||||
""""Stop down everything that was started."""
|
||||
@ -38,7 +39,7 @@ class TestNotifySmtp(unittest.TestCase):
|
||||
'Content-Transfer-Encoding: 7bit\n'
|
||||
'Subject: Home Assistant\n'
|
||||
'To: recip1@example.com,testrecip@test.com\n'
|
||||
'From: test@test.com\n'
|
||||
'From: HomeAssistant <test@test.com>\n'
|
||||
'X-Mailer: HomeAssistant\n'
|
||||
'Date: [^\n]+\n'
|
||||
'Message-Id: <[^@]+@[^>]+>\n'
|
||||
@ -52,3 +53,24 @@ class TestNotifySmtp(unittest.TestCase):
|
||||
msg = self.mailer.send_message('Test msg',
|
||||
data={'images': ['test.jpg']})
|
||||
self.assertTrue('Content-Type: multipart/related' in msg)
|
||||
|
||||
@patch('email.utils.make_msgid', return_value='<mock@mock>')
|
||||
def test_html_email(self, mock_make_msgid):
|
||||
"""Test build of html email behavior."""
|
||||
html = '''
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Intruder alert at apartment!!</h1>
|
||||
</div>
|
||||
<div>
|
||||
<img alt="test.jpg" src="cid:test.jpg"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>'''
|
||||
msg = self.mailer.send_message('Test msg',
|
||||
data={'html': html,
|
||||
'images': ['test.jpg']})
|
||||
self.assertTrue('Content-Type: multipart/related' in msg)
|
||||
|
Loading…
x
Reference in New Issue
Block a user