Skip to content

fix(email): add SMTP security mode configuration - #42907

Open
sayurinana wants to merge 1 commit into
NousResearch:mainfrom
sayurinana:fix/email-smtp-security-mode
Open

fix(email): add SMTP security mode configuration#42907
sayurinana wants to merge 1 commit into
NousResearch:mainfrom
sayurinana:fix/email-smtp-security-mode

Conversation

@sayurinana

Copy link
Copy Markdown

What does this PR do?

Adds shared SMTP transport-security handling for Hermes email sends.

By default, EMAIL_SMTP_SECURITY=auto preserves the existing STARTTLS path for port 587 and other non-465 ports, while using implicit TLS / smtplib.SMTP_SSL for port 465. It also adds explicit override modes for deployments that need to force either behavior:

  • EMAIL_SMTP_SECURITY=auto
  • EMAIL_SMTP_SECURITY=starttls
  • EMAIL_SMTP_SECURITY=implicit_tls

This fixes email providers that require implicit TLS on SMTP port 465 without breaking existing STARTTLS setups.

Related Issue

No linked issue. This is a direct bug-fix PR from a forked branch.

Related PRs reviewed while checking for duplicates: #42026, #13564, and #12161. This PR differs by adding a shared configurable policy used by both gateway email replies and one-shot email sends, plus docs/config metadata and focused tests.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Add gateway/email_smtp.py as the shared SMTP security policy/helper.
  • Update the email gateway adapter to use the shared policy for SMTP replies.
  • Update one-shot email sends in tools/send_message_tool.py to use the same policy.
  • Add optional EMAIL_SMTP_SECURITY metadata and .env.example documentation.
  • Update English and Chinese email/environment-variable docs.
  • Update environment propagation/blocklist coverage for the new email env var.
  • Add targeted tests for:
    • auto mode using implicit TLS / SMTP_SSL on port 465;
    • auto mode preserving SMTP + STARTTLS on port 587 and other non-465 ports;
    • explicit starttls and implicit_tls overrides;
    • accepted aliases and clear failure on invalid/ambiguous values;
    • gateway and one-shot email send call sites.

How to Test

Targeted email/config/local-env suites:

env -u HERMES_EXEC_ASK -u HERMES_SESSION_PLATFORM -u HERMES_GATEWAY_SESSION -u HERMES_INTERACTIVE -u HERMES_YOLO_MODE python -m pytest tests/gateway/test_email.py tests/gateway/test_email_smtp_security_policy.py tests/tools/test_send_message_email_smtp_security.py tests/hermes_cli/test_web_server.py tests/tools/test_local_env_blocklist.py -q

Result:

385 passed in 37.51s

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu 24.04/Linux 6.17, Python 3.11.15, pytest 9.0.2

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
    • N/A: this adds an environment variable, not a config key; .env.example and env-var docs were updated.
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
    • N/A: no contributor workflow or architecture guide change required.
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A
    • N/A: the email send behavior changes internally; no tool schema change was required.

For New Skills

N/A — this PR does not add a skill.

Screenshots / Logs

No screenshots. Targeted test output is included in How to Test.

@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists platform/email Email (IMAP/SMTP) adapter comp/gateway Gateway runner, session dispatch, delivery labels Jun 9, 2026
@alt-glitch alt-glitch added comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/tools Tool registry, model_tools, toolsets area/config Config system, migrations, profiles labels Jun 26, 2026

@teknium1 teknium1 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.

Thanks for covering both reply and one-shot SMTP paths. The port-465 premise still matters for one-shot sends, but this patch needs to be salvaged onto the current plugin architecture.

Problems

  • gateway/platforms/email.py and tools/send_message_tool.py::_send_email are no longer active paths after 560010547. Current one-shot delivery is plugins/platforms/email/adapter.py:_standalone_send(); it still forces SMTP() + STARTTLS at plugins/platforms/email/adapter.py:1225-1226.
  • Current gateway replies already select SMTP_SSL for port 465 in plugins/platforms/email/adapter.py:509-549 and retain an IPv4 fallback. The proposed gateway/email_smtp.py helper does not retain that fallback, so it cannot replace the current helper unchanged.

Suggested changes

  • Port the shared policy into the current plugin adapter, including _standalone_send(), while preserving the existing IPv4 and TLS-verification behavior.
  • Add tests for the current registered one-shot path on port 465 and retain the existing fallback coverage.

Automated hermes-sweeper review.

Comment thread tools/send_message_tool.py Outdated
server.login(address, password)
server.send_message(msg)
server.quit()
server = open_smtp_connection(

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.

This is now a legacy call site. Current main dispatches email through the registered plugin at tools/send_message_tool.py:1068, and the active one-shot implementation is plugins/platforms/email/adapter.py:_standalone_send (:1190), which still uses SMTP() plus STARTTLS at :1225-1226. Port this policy to that plugin path.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the pointer - done. The policy is now ported to the plugin path (plugins/platforms/email/adapter.py), and both SMTP entry points in the adapter share it:

  • _standalone_send (the active one-shot path, registered as the plugin's standalone_sender_fn and dispatched via the registry from tools/send_message_tool.py:1068): now uses a shared open_smtp_connection() instead of the hard-coded SMTP() + STARTTLS. auto picks SMTP_SSL on 465 and SMTP+STARTTLS elsewhere; starttls/implicit_tls override explicitly. login/send_message wrapped in try/finally.
  • EmailAdapter._connect_smtp (persistent gateway path): port == 465 replaced by resolve_smtp_security(), IPv4 fallback preserved.
  • The shared helpers (normalize_smtp_security / resolve_smtp_security / open_smtp_connection) live inside the email plugin module rather than a new core gateway/email_smtp.py, keeping the plugin self-contained.

Config housekeeping: EMAIL_SMTP_SECURITY is now bridged to config.yaml (platforms.email.smtp_security via PlatformConfig.extra) as canonical, env var kept as backwards-compatible bridge (terminal.cwd -> TERMINAL_CWD precedent). Docs point to config.yaml.

Tests: tests/plugins/platforms/email/test_email_smtp_security.py (49 cases); tests/gateway/test_email.py still passes (88, no regression).

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
Port the EMAIL_SMTP_SECURITY / smtp_security policy to the email plugin
adapter path (plugins/platforms/email/adapter.py), per maintainer review on
PR NousResearch#42907: the legacy tools/send_message_tool.py:_send_email call site has
been superseded by the registered plugin's _standalone_send, which still
hard-coded SMTP()+STARTTLS.

Changes:
- Add normalize_smtp_security / resolve_smtp_security / open_smtp_connection
  as a shared SMTP security policy inside the email plugin adapter module
  (not a core gateway module), so the policy is self-contained in the plugin.
- _connect_smtp (persistent gateway path) now resolves the mode via
  resolve_smtp_security instead of hard-coding port==465; the existing IPv4
  fallback is preserved.
- _standalone_send (one-shot path) now uses open_smtp_connection with a
  try/finally around login+send_message, fixing the implicit-TLS/port-465
  gap the maintainer flagged.
- smtp_security is read canonically from config.yaml
  (platforms.email.smtp_security via PlatformConfig.extra); EMAIL_SMTP_SECURITY
  env is kept as a backwards-compatible bridge (terminal.cwd -> TERMINAL_CWD
  precedent, AGENTS.md). User-facing docs point to config.yaml.
- Add EMAIL_SMTP_SECURITY to the plugin's optional_env manifest, .env.example,
  the dashboard env reveal/description, and the local terminal env blocklist.
- Document the new setting in the email user guide and environment-variables
  reference (EN + zh-Hans).

Tests: tests/plugins/platforms/email/test_email_smtp_security.py covers
normalize/resolve/open_smtp_connection/_standalone_send (auto port-based,
explicit overrides, aliases, rejection of ambiguous values, STARTTLS-failure
cleanup, env bridge). 49 passed; existing tests/gateway/test_email.py 88
passed (no regression).
@sayurinana
sayurinana force-pushed the fix/email-smtp-security-mode branch from 526c1f2 to b1a4986 Compare July 14, 2026 12:12
@ydmw74

ydmw74 commented Aug 19, 2026

Copy link
Copy Markdown

Real-world use case supporting this approach: we run the Hermes email adapter against an SMTP endpoint that requires implicit TLS on the non-standard port 10465.

The current port == 465 inference required us to maintain a local workaround:

if port in (465, 10465):
    server = smtplib.SMTP_SSL(host, port, context=context)

The explicit EMAIL_SMTP_SECURITY=implicit_tls mode proposed here would remove that hard-coded workaround and correctly support SMTPS endpoints exposed on custom ports (for example through provider-specific gateways or proxies). Please retain the explicit security override independently of the configured port; auto can remain backward-compatible with the existing port-based behavior.

We would be happy to validate this PR against our port-10465 deployment.

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

Labels

area/config Config system, migrations, profiles comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/gateway Gateway runner, session dispatch, delivery comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists platform/email Email (IMAP/SMTP) adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants