Skip to content

feat(email): HTML email support with Markdown conversion - #46642

Closed
swissly wants to merge 2 commits into
NousResearch:mainfrom
swissly:feat/html-email-support
Closed

feat(email): HTML email support with Markdown conversion#46642
swissly wants to merge 2 commits into
NousResearch:mainfrom
swissly:feat/html-email-support

Conversation

@swissly

@swissly swissly commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds optional HTML email formatting by converting Markdown bodies into styled HTML and attaching both plain-text and HTML parts.

Changes

  • Convert Markdown bodies to styled HTML with inline CSS
  • multipart/alternative emails (plain text + HTML)
  • Config toggle: platforms.email.html_format: true (default: enabled)
  • Gmail/Outlook compatible inline styles
  • Graceful fallback: HTML conversion failure -> plain text only

Configuration

platforms:
  email:
    html_format: true    # enable HTML emails (default: true)

Security Note

The Markdown library passes through raw HTML by default. We intentionally do NOT sanitize with bleach/allowlists because:

  1. Hermes generates its own email bodies (LLM output, not user input)
  2. Emails are sent to the configured address (self-to-self)
  3. The config toggle provides a kill-switch: set html_format: false

Testing

  • ✅ Syntax check passed
  • ✅ HTML email generation verified
  • ✅ Graceful fallback on conversion failure

Related

- Convert Markdown bodies to styled HTML with inline CSS
- multipart/alternative emails (plain text + HTML)
- Config toggle: platforms.email.html_format (default: true)
- Gmail/Outlook compatible inline styles
- Graceful fallback: HTML conversion failure -> plain text only
- Security: no bleach needed for self-to-self emails

Closes NousResearch#11941

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds optional HTML-formatted email sending with inline styles by converting the existing Markdown body to a styled HTML variant and attaching it alongside the plain-text email.

Changes:

  • Introduces HTML email wrapper markup (header/footer) plus a tag-to-inline-style mapping.
  • Adds _style_html_email() to inject inline element styles via regex substitutions.
  • Adds html_format config (default enabled) and sends multipart plain + HTML (with Markdown conversion) when enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +103 to +110
for tag, style in _HERMES_EMAIL_ELEMENT_STYLES:
# Skip tags that already have a style attribute to avoid duplication
# Use negative lookahead: only match <tag that is NOT followed by ...style=
html = re.sub(
rf'<{tag}(?!\s+style=)(\s|>)',
rf'<{tag} {style}\1',
html,
)
Comment thread gateway/platforms/email.py Outdated
Comment on lines +51 to +60
_HERMES_EMAIL_CSS = """\
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#f4f4f7;">
<div style="max-width:680px;margin:0 auto;background:#ffffff;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
font-size:15px;line-height:1.6;color:#2d3748;padding:32px;">

"""
Comment on lines +648 to +654
if self._html_format:
try:
import markdown as _md
html_body = _md.markdown(
body,
extensions=["tables", "fenced_code", "nl2br"],
)
Comment thread gateway/platforms/email.py Outdated
Comment on lines +644 to +657
# Plain text fallback
msg.attach(MIMEText(body, "plain", "utf-8"))

# HTML version with inline CSS (if enabled)
if self._html_format:
try:
import markdown as _md
html_body = _md.markdown(
body,
extensions=["tables", "fenced_code", "nl2br"],
)
html_body = _style_html_email(html_body)
html_email = _HERMES_EMAIL_CSS + html_body + _HERMES_EMAIL_FOOTER
msg.attach(MIMEText(html_email, "html", "utf-8"))
Comment on lines +345 to +352
# SECURITY NOTE: The Markdown library passes through raw HTML by default.
# We intentionally do NOT sanitize with bleach/allowlists because:
# 1. Hermes generates its own email bodies (LLM output, not user input)
# 2. Emails are sent to the configured address (self-to-self)
# 3. The config toggle provides a kill-switch: set html_format: false
# If the threat model changes (e.g. forwarding untrusted content),
# add bleach.clean() here with an allowed-tags list.
self._html_format = extra.get("html_format", True)
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/email Email (IMAP/SMTP) adapter P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jun 15, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #46619 — same Markdown-to-HTML multipart/alternative feature for the email gateway (gateway/platforms/email.py), with #46619 filed earlier. Both implement the html_format toggle and inline-CSS rendering. Related: #46626 (closed, bundled with SMTP fix), #11941 (feature request).

- Fix regex: check style= anywhere in tag with [^>]* lookahead
- Rename _HERMES_EMAIL_CSS to _HTML_PREFIX (accurate naming)
- Validate markdown dependency once in __init__, auto-disable if missing
- Wrap plain+HTML in multipart/alternative container (RFC correct)
- Security: add HTML escaping note in comment
@swissly

swissly commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Addressed Copilot Review Round 3

Fixes Applied

  1. Regex style= check - Now uses [^>]* lookahead to catch style= anywhere in tag
  2. Naming - Renamed _HERMES_EMAIL_CSS to _HTML_PREFIX (accurate)
  3. markdown validation - Checks once in init, auto-disables HTML if missing
  4. multipart/alternative - Plain+HTML wrapped in proper alternative container
  5. Security note - Documented in code comment (threat model, self-to-self, kill-switch)

Verification

  • Syntax check passed
  • markdown validated at init time
  • RFC-correct multipart/alternative structure

@swissly

swissly commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Closing as duplicate of #46619 (filed earlier).

Contributing Copilot review fixes to #46619 instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have platform/email Email (IMAP/SMTP) adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: HTML email support for email gateway (multipart/alternative + Markdown rendering)

3 participants