feat(email): HTML rendering for attachment send paths - #54073
Conversation
Add _attach_body, _create_body_part, _attach_parts helper methods to EmailAdapter so multipart/alternative HTML emails work on all 3 send paths (_send_email, _send_email_with_attachment, _send_email_with_attachments). Previously only _send_email supported HTML. The attachment paths sent plain text only. Supersedes NousResearch#46619 (which targeted the old gateway/platforms/email.py path). Refs: NousResearch#11941
|
Closing: checklist review found scope violations (bundled unrelated openviking change + duplicated HTML logic). Reopening clean PR. |
There was a problem hiding this comment.
Pull request overview
Adds HTML email rendering/styling support to the email platform adapter’s outbound send paths (including attachment sends) so recipients can see rich formatting via multipart/alternative rather than plain text only. Also introduces an OpenViking viking_add_resource cost warning when adding a full GitHub repository URL.
Changes:
- Add HTML wrapper + inline-style injection and an
html_formatconfig flag to enable/disable HTML email bodies. - Wrap attachment send bodies in
multipart/alternativefor proper plain+HTML behavior insidemultipart/mixed. - Add GitHub repo-root URL detection + cost warnings to the OpenViking add-resource tool schema/response.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| plugins/platforms/email/adapter.py | Adds HTML email rendering/styling helpers and updates send paths to include optional HTML parts. |
| plugins/memory/openviking/init.py | Adds GitHub repo URL detection and emits cost warnings when queueing full-repo resources. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if self._html_format: | ||
| try: | ||
| import markdown | ||
| html_body = markdown.markdown( | ||
| body, extensions=["tables", "fenced_code", "nl2br"] | ||
| ) | ||
| html_body = _style_html_email(_HTML_PREFIX + html_body + _HERMES_EMAIL_FOOTER) | ||
| msg.attach(MIMEText(body, "plain", "utf-8")) | ||
| msg.attach(MIMEText(html_body, "html", "utf-8")) | ||
| except ImportError: | ||
| logger.debug("[Email] markdown not installed, sending plain text") | ||
| msg.attach(MIMEText(body, "plain", "utf-8")) | ||
| except Exception as e: | ||
| logger.warning("[Email] HTML conversion failed, falling back to plain: %s", e) | ||
| msg.attach(MIMEText(body, "plain", "utf-8")) | ||
| else: | ||
| msg.attach(MIMEText(body, "plain", "utf-8")) |
| def _is_github_repo_url(url: str) -> bool: | ||
| """Check if URL is a GitHub repo root (not a specific file/blob/tree).""" | ||
| from urllib.parse import urlparse | ||
| parsed = urlparse(url) | ||
| if not parsed.hostname or not parsed.hostname.endswith("github.com"): | ||
| return False | ||
| parts = [p for p in parsed.path.strip("/").split("/") if p] | ||
| # github.com/owner/repo — exactly 2 path segments, no /blob/ or /tree/ | ||
| if len(parts) != 2: | ||
| return False | ||
| # Normalize .git suffix (owner/repo.git → owner/repo) | ||
| repo = parts[1].removesuffix(".git") | ||
| return bool(repo) |
| # email: | ||
| # skip_attachments: true | ||
| self._skip_attachments = extra.get("skip_attachments", False) | ||
| self._html_format = extra.get("html_format", True) |
| ADD_RESOURCE_SCHEMA = { | ||
| "name": "viking_add_resource", | ||
| "description": ( | ||
| "Add a remote URL or local file/directory to the OpenViking knowledge base. " | ||
| "Remote resources must be public http(s), git, or ssh URLs. " |
Related: supersedes closed #46619 (which targeted the pre-migration |
Add HTML email rendering to _send_email_with_attachment and _send_email_with_attachments. Previously only _send_email supported multipart/alternative with HTML; attachment paths sent plain text only. - Add _attach_body/_create_body_part/_attach_parts helpers - Add _style_html_email with inline CSS for Gmail/Outlook compat - Add _HTML_PREFIX/_HERMES_EMAIL_FOOTER HTML wrapper templates - Add html_format config option (default: true, opt-out: false) - Both attachment paths now use _create_body_part for HTML support - _send_email uses _attach_body (consolidated, no duplicated logic) Lazy markdown import — adapter works without markdown installed. Graceful fallback: conversion failure → plain text + warning. Supersedes NousResearch#46619 (old gateway path) and NousResearch#54073 (bundled scope). Refs: NousResearch#11941, NousResearch#36853
Summary
Add HTML email rendering to the 2 send paths that were missing it:
_send_email_with_attachmentand_send_email_with_attachments.Previously, only
_send_email(plain replies) supportedmultipart/alternativewith HTML. The attachment paths sent plain text only, so emails with file attachments never got rich formatting.Changes
plugins/platforms/email/adapter.py:_attach_body()— attach plain+HTML to a message_create_body_part()— createmultipart/alternativebody for use insidemultipart/mixed_attach_parts()— shared helper: plain text always, HTML whenhtml_format: true_send_email_with_attachment: use_create_body_part(body)instead of bareMIMEText_send_email_with_attachments: sameSupersedes
Closes #46619 (which targeted the old
gateway/platforms/email.pypath before the plugin migration).Refs: #11941