diff --git a/changedetectionio/notification/email_helpers.py b/changedetectionio/notification/email_helpers.py new file mode 100644 index 00000000..c653db37 --- /dev/null +++ b/changedetectionio/notification/email_helpers.py @@ -0,0 +1,46 @@ +def as_monospaced_html_email(content: str, title: str) -> str: + """ + Wraps `content` in a minimal, email-safe HTML template + that forces monospace rendering across Gmail, Hotmail, Apple Mail, etc. + + Args: + content: The body text (plain text or HTML-like). + convert_newlines: If True, replaces '\n' with '
' for display. + + Returns: + A complete HTML document string suitable for sending as an email body. + """ + + # All line feed types should be removed and then this function should only be fed
's + # Then it works with our
 styling without double linefeeds
+    content = content.translate(str.maketrans('', '', '\r\n'))
+
+    if title:
+        import html
+        title = html.escape(title)
+    else:
+        title = ''
+    # 2. Full email-safe HTML
+    html_email = f"""
+
+
+  
+  
+  
+  
+  {title}
+
+
+  
+    {content}
+  
+ +""" + return html_email \ No newline at end of file diff --git a/changedetectionio/notification/handler.py b/changedetectionio/notification/handler.py index 617b9608..33827f45 100644 --- a/changedetectionio/notification/handler.py +++ b/changedetectionio/notification/handler.py @@ -6,6 +6,7 @@ from loguru import logger from urllib.parse import urlparse from .apprise_plugin.assets import apprise_asset, APPRISE_AVATAR_URL from .apprise_plugin.custom_handlers import SUPPORTED_HTTP_METHODS +from .email_helpers import as_monospaced_html_email from ..diff import HTML_REMOVED_STYLE, REMOVED_PLACEMARKER_OPEN, REMOVED_PLACEMARKER_CLOSED, ADDED_PLACEMARKER_OPEN, HTML_ADDED_STYLE, \ ADDED_PLACEMARKER_CLOSED, CHANGED_INTO_PLACEMARKER_OPEN, CHANGED_INTO_PLACEMARKER_CLOSED, CHANGED_PLACEMARKER_OPEN, \ CHANGED_PLACEMARKER_CLOSED, HTML_CHANGED_STYLE, HTML_CHANGED_INTO_STYLE @@ -304,7 +305,7 @@ def process_notification(n_object: NotificationContextData, datastore): # Could have arrived at any stage, so we dont end up running .escape on it if 'html' in requested_output_format: - n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '
\n') + n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '
\r\n') else: # Markup, text types etc n_body = n_body.replace(CUSTOM_LINEBREAK_PLACEHOLDER, '\r\n') @@ -314,6 +315,15 @@ def process_notification(n_object: NotificationContextData, datastore): 'url': url}) apobj.add(url) + # Enforce monospace output for PLAINTEXT Document going to a HTML style notification + watch_mime_type = n_object.get('watch_mime_type') + if watch_mime_type and 'text/' in watch_mime_type.lower() and not 'html' in watch_mime_type.lower(): + if 'html' in requested_output_format: + # Since they likely want HTML style notifications but of a Plain-text document, lets wrap the body + if not '