Files
hermes-skills/skills/email/email-workflows/references/daily-tech-digest-email.md
T

3.1 KiB

Daily Tech Digest — HTML Email with Clickable Headlines

The script at /root/.hermes/scripts/daily-feed-summary.py collects RSS feeds (XDA, MakeUseOf, How-To Geek, 9to5Mac, HotHardware, Gizmodo, Top Gear) and sends Germaine a daily digest.

Problem: headlines not clickable

The original script sent a MIMEText(body, 'plain') message. Markdown links [title](url) rendered as literal text — the user had to copy-paste URLs manually.

Fix: multipart/alternative with markdown-to-HTML conversion

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import re

def markdown_to_html(text):
    """Convert markdown links, bold, italic, headers to email-safe HTML."""
    # Links: [text](url) → <a href="url">text</a>
    html = re.sub(
        r'\[([^\]]+)\]\(([^)]+)\)',
        r'<a href="\2" style="color:#2563eb;text-decoration:underline;">\1</a>',
        text
    )
    # Bold: **text** → <strong>text</strong>
    html = re.sub(r'\*\*([^*]+)\*\*', r'<strong>\1</strong>', html)
    # Italic: _text_ → <em>text</em>
    html = re.sub(r'_([^_]+)_', r'<em>\1</em>', html)
    # H2: ## Header
    html = re.sub(r'^## (.+)$', r'<h2 style="font-size:16px;margin:16px 0 4px;color:#1a1a2e;">\1</h2>', html, flags=re.MULTILINE)
    # H1: # Header
    html = re.sub(r'^# (.+)$', r'<h1 style="font-size:20px;margin:0 0 8px;color:#1a1a2e;">\1</h1>', html, flags=re.MULTILINE)
    # HR: ---
    html = re.sub(r'^---+$', r'<hr style="border:none;border-top:1px solid #e2e8f0;margin:16px 0;">', html, flags=re.MULTILINE)
    return html

def send_html_digest(plain_body, subject):
    """Send multipart/alternative digest."""
    html_content = f'''<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
<style>
body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1.6;color:#333;padding:20px;max-width:640px;margin:0 auto;}}
a{{color:#2563eb;}}p{{margin:4px 0;}}
</style></head><body>
<pre style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1.6;white-space:pre-wrap;word-wrap:break-word;">{plain_body}</pre>
</body></html>'''

    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = 'g@germainebrown.com'
    msg['To'] = 'g@germainebrown.com'
    msg.attach(MIMEText(plain_body, 'plain', 'utf-8'))
    msg.attach(MIMEText(html_content, 'html', 'utf-8'))
    # ... SMTP send via port 2525

Key design decisions

  • multipart/alternative — includes both text/plain (for clients that can't render HTML) and text/html (for clickable links)
  • Inline styles — email clients strip <style> blocks, so font, color, and spacing are applied inline via the <pre> element
  • white-space: pre-wrap — preserves the monospaced markdown layout in HTML (indentation, line breaks, table alignment)
  • Color #2563eb (blue) — standard link color, renders well in both light and dark email themes

SMTP config

Using mail.germainebrown.com:2525 with STARTTLS. Port 587 is blocked from the netcup VPS.