Files
hermes-agent/website/docs/user-guide/messaging/email.md
kshitijk4poor 4b4362520b docs(email): authserv_id is required for authenticated-sender mode
The guide still told users to "leave authserv_id unset" when their
server's host varies, which now drops every message that needs an
authenticated From:. State that the pin is required, how to find it
(the token before the first ';' of the topmost Authentication-Results
header your server writes), what to do when it seems to vary (RFC 8601
authserv-id names the administrative domain, so configure a stable one
and pin it), and that the only alternative is the explicit
require_authenticated_sender: false / EMAIL_TRUST_FROM_HEADER=true
opt-out. Mirror the reference tables, plugin.yaml prompt and zh-Hans
translation.

Co-authored-by: JiuYue0820 <287222957+FirmaSpring@users.noreply.github.com>
2026-10-04 14:06:56 +05:30

13 KiB

sidebar_position, title, description
sidebar_position title description
7 Email Set up Hermes Agent as an email assistant via IMAP/SMTP

Email Setup

Hermes can receive and reply to emails using standard IMAP and SMTP protocols. Send an email to the agent's address and it replies in-thread — no special client or bot API needed. Works with Gmail, Outlook, Yahoo, Fastmail, or any provider that supports IMAP/SMTP.

:::info Gateway adapter only: no external dependencies This page covers the Email gateway adapter, which uses Python's built-in imaplib, smtplib, and email modules. No additional packages or external services are required for this gateway path. :::

This is separate from the bundled Himalaya email skill, which lets the agent manage email through terminal commands and requires the external himalaya CLI plus a Himalaya config file.

Use case What to configure External dependency
Let people email the Hermes agent and receive replies Email gateway adapter on this page None beyond an IMAP/SMTP email account
Let the agent inspect, compose, move, and manage mailbox messages from terminal tools Himalaya email skill himalaya CLI and ~/.config/himalaya/config.toml

Prerequisites

  • A dedicated email account for your Hermes agent (don't use your personal email)
  • IMAP enabled on the email account
  • An app password if using Gmail or another provider with 2FA

Gmail Setup

  1. Enable 2-Factor Authentication on your Google Account
  2. Go to App Passwords
  3. Create a new App Password (select "Mail" or "Other")
  4. Copy the 16-character password — you'll use this instead of your regular password

Outlook / Microsoft 365

  1. Go to Security Settings
  2. Enable 2FA if not already active
  3. Create an App Password under "Additional security options"
  4. IMAP host: outlook.office365.com, SMTP host: smtp.office365.com

Other Providers

Most email providers support IMAP/SMTP. Check your provider's documentation for:

  • IMAP host and port (usually port 993 with SSL)
  • SMTP host and port (usually port 587 with STARTTLS)
  • Whether app passwords are required

Proton Mail Bridge / local relays

Proton Mail Bridge (and similar local relays such as a self-hosted MTA) listen on loopback with STARTTLS and a self-signed certificate, so the defaults (implicit TLS on IMAP 993, verified certificates) won't connect. Override the transport in ~/.hermes/config.yaml:

platforms:
  email:
    enabled: true
    extra:
      imap_host: 127.0.0.1
      imap_security: starttls     # tls (default) | starttls | plain
      imap_tls_verify: false      # Bridge uses a self-signed cert
      smtp_host: 127.0.0.1
      smtp_security: starttls     # default: tls on port 465, starttls otherwise
      smtp_tls_verify: false

and set EMAIL_IMAP_PORT=1143 / EMAIL_SMTP_PORT=1025 alongside your Bridge credentials in ~/.hermes/.env. Unknown *_security values log a warning and fall back to the secure default. Only disable *_tls_verify for loopback hosts — Hermes logs a warning when verification is off for any other host.


Step 1: Configure Hermes

The easiest way:

hermes gateway setup

Select Email from the platform menu. The wizard prompts for your email address, password, IMAP/SMTP hosts, and allowed senders.

Manual Configuration

Add to ~/.hermes/.env:

# Required
EMAIL_ADDRESS=hermes@gmail.com
EMAIL_PASSWORD=abcd efgh ijkl mnop    # App password (not your regular password)
EMAIL_IMAP_HOST=imap.gmail.com
EMAIL_SMTP_HOST=smtp.gmail.com

# Security (recommended)
EMAIL_ALLOWED_USERS=your@email.com,colleague@work.com
# Required: exact authserv-id on your receiving server's topmost
# Authentication-Results header (Gmail: mx.google.com).
# That server must strip inbound headers claiming this id before adding its own.
EMAIL_AUTHSERV_ID=mx.google.com

# Optional
EMAIL_IMAP_PORT=993                    # Default: 993 (IMAP SSL)
EMAIL_SMTP_PORT=587                    # Default: 587 (SMTP STARTTLS)
EMAIL_POLL_INTERVAL=15                 # Seconds between inbox checks (default: 15)
EMAIL_HOME_ADDRESS=your@email.com      # Default delivery target for cron jobs

Step 2: Start the Gateway

hermes gateway              # Run in foreground
hermes gateway install      # Install as a user service
sudo hermes gateway install --system   # Linux only: boot-time system service

On startup, the adapter:

  1. Tests IMAP and SMTP connections
  2. Marks all existing inbox messages as "seen" (only processes new emails)
  3. Starts polling for new messages

How It Works

Receiving Messages

The adapter polls the IMAP inbox for UNSEEN messages at a configurable interval (default: 15 seconds). For each new email:

  • Subject line is included as context (e.g., [Subject: Deploy to production])
  • Reply emails (subject starting with Re:) skip the subject prefix — the thread context is already established
  • Attachments are cached locally:
    • Images (JPEG, PNG, GIF, WebP) → available to the vision tool
    • Documents (PDF, ZIP, etc.) → available for file access
  • HTML-only emails have tags stripped for plain text extraction
  • Self-messages are filtered out to prevent reply loops
  • Automated/noreply senders are silently ignored — noreply@, mailer-daemon@, bounce@, no-reply@, and emails with Auto-Submitted, Precedence: bulk, or List-Unsubscribe headers

Sending Replies

Replies are sent via SMTP with proper email threading:

  • In-Reply-To and References headers maintain the thread
  • Subject line preserved with Re: prefix (no double Re: Re:)
  • Message-ID generated with the agent's domain
  • Responses are sent as plain text (UTF-8)

File Attachments

The agent can send file attachments in replies. Include MEDIA:/path/to/file in the response and the file is attached to the outgoing email.

Skipping Attachments

To ignore all incoming attachments (for malware protection or bandwidth savings), add to your config.yaml:

platforms:
  email:
    skip_attachments: true

When enabled, attachment and inline parts are skipped before payload decoding. The email body text is still processed normally.


Access Control

Email access is stricter by default than chat-style platforms:

  1. EMAIL_ALLOWED_USERS set → only emails from those addresses (and from GATEWAY_ALLOWED_USERS or an approved pairing) are processed
  2. No allowlist set → unknown senders are ignored silently
  3. EMAIL_ALLOW_ALL_USERS=true → any sender is accepted (use with caution)
  4. platforms.email.unauthorized_dm_behavior: pair → unknown senders receive a pairing code
  5. platforms.email.unauthorized_dm_behavior: decline → an unknown sender receives one polite refusal, then nothing more for 24 hours

Allowlist entries match whole addresses. A bare entry such as alice (a chat username in GATEWAY_ALLOWED_USERS, say) never admits alice@ at any domain, and mail from such an address is dropped rather than paired or declined.

Hermes acts on a message only when the Authentication-Results header stamped by your receiving server authenticates its From: domain (DMARC, or aligned SPF/DKIM). This applies with open access (EMAIL_ALLOW_ALL_USERS / GATEWAY_ALLOW_ALL_USERS) too: open access admits any sender, but each conversation and every reply is keyed on From:, so a forged From: would put the attacker's text into that address's session and make Hermes mail it. Pairing codes and declines likewise need an authenticated From:, so neither is mailed to a forged address. If your mail server does not stamp that header, set platforms.email.require_authenticated_sender: false (or EMAIL_TRUST_FROM_HEADER=true) to accept the risk; otherwise mail from an admitted sender that carries no such header is dropped with a Dropping sender with unauthenticated From warning in the gateway log. The same warning, pointing at platforms.email.authserv_id (EMAIL_AUTHSERV_ID) instead, fires when the topmost Authentication-Results header (the one your server prepends; lower ones are never consulted) does not name exactly the pinned authserv_id. authserv_id must equal exactly the authserv-id your server writes before the first ; of that header. Mail from an address listed in EMAIL_ALLOWED_USERS / GATEWAY_ALLOWED_USERS that fails authentication otherwise (for example a sender whose DKIM is broken, or a forwarder that breaks alignment) is dropped with the same warning and its verdict, with no opt-out hint. Other senders' failing mail, such as forged From: under open access, is dropped quietly, logged at debug level only.

The authserv_id pin is required while sender authentication is on. Without platforms.email.authserv_id (EMAIL_AUTHSERV_ID), Hermes cannot tell your server's Authentication-Results header from one the sender wrote (a self-hosted MTA that adds none of its own leaves the sender's on top), so it trusts none: every message that needs an authenticated From: is dropped, and the gateway log says so once, with the fix. To find the value, open a message your server delivered ("show original" / raw source) and copy the token before the first ; of the topmost Authentication-Results header, for example mx.google.com for Gmail. If that token seems to change between messages, it is a per-relay hostname rather than an authserv-id: RFC 8601 defines the authserv-id as a stable name for the administrative domain, so configure your MTA to stamp one (for example AuthservID in OpenDKIM / OpenDMARC) and pin that. A matching id is a pin, not proof of provenance: confirm your MTA strips inbound Authentication-Results headers claiming its id before it prepends its own. If you cannot pin a stable id, opt out explicitly with platforms.email.require_authenticated_sender: false (or EMAIL_TRUST_FROM_HEADER=true) and accept that From: is then trusted as written.

:::warning Use a dedicated inbox and configure EMAIL_ALLOWED_USERS for normal operation. Email pairing is opt-in because shared inboxes often contain unrelated unread messages, and Hermes should not reply to those contacts by default. :::


Troubleshooting

Problem Solution
"IMAP connection failed" at startup Verify EMAIL_IMAP_HOST and EMAIL_IMAP_PORT. Ensure IMAP is enabled on the account. For Gmail, enable it in Settings → Forwarding and POP/IMAP.
"SMTP connection failed" at startup Verify EMAIL_SMTP_HOST and EMAIL_SMTP_PORT. Check that your password is correct (use App Password for Gmail).
Messages not received Check EMAIL_ALLOWED_USERS includes the sender's email. If the gateway log shows authserv-id is not configured, set EMAIL_AUTHSERV_ID to the exact id on the topmost Authentication-Results header your server writes. Check spam folder — some providers flag automated replies.
"Authentication failed" For Gmail, you must use an App Password, not your regular password. Ensure 2FA is enabled first.
Duplicate replies Ensure only one gateway instance is running. Check hermes gateway status.
Slow response The default poll interval is 15 seconds. Reduce with EMAIL_POLL_INTERVAL=5 for faster response (but more IMAP connections).
Replies not threading The adapter uses In-Reply-To headers. Some email clients (especially web-based) may not thread correctly with automated messages.

Security

:::warning Use a dedicated email account. Don't use your personal email — the agent stores the password in .env and has full inbox access via IMAP. :::

  • Use App Passwords instead of your main password (required for Gmail with 2FA)
  • Set EMAIL_ALLOWED_USERS to restrict who can interact with the agent
  • Set EMAIL_AUTHSERV_ID to the exact id of the receiving MTA's topmost Authentication-Results stamp, and verify that MTA strips inbound headers claiming that id
  • The password is stored in ~/.hermes/.env — protect this file (chmod 600)
  • IMAP uses SSL (port 993) and SMTP uses STARTTLS (port 587) by default — connections are encrypted

Environment Variables Reference

Variable Required Default Description
EMAIL_ADDRESS Yes — Agent's email address
EMAIL_PASSWORD Yes — Email password or app password
EMAIL_IMAP_HOST Yes — IMAP server host (e.g., imap.gmail.com)
EMAIL_SMTP_HOST Yes — SMTP server host (e.g., smtp.gmail.com)
EMAIL_IMAP_PORT No 993 IMAP server port
EMAIL_SMTP_PORT No 587 SMTP server port
EMAIL_POLL_INTERVAL No 15 Seconds between inbox checks
EMAIL_ALLOWED_USERS No — Comma-separated allowed sender addresses
EMAIL_AUTHSERV_ID Yes, unless sender authentication is off — Exact authserv-id on your receiving server's topmost Authentication-Results header; without it every message needing an authenticated From: is dropped
EMAIL_HOME_ADDRESS No — Default delivery target for cron jobs
EMAIL_ALLOW_ALL_USERS No false Allow all senders (not recommended)