Hermes Email++
Hermes Email++ is a third-party IMAP/SMTP platform adapter for Hermes Agent.
It is registered as email_pp, independently of Hermes' built-in email
platform. It routes each RFC email thread to its own Hermes session, sends
plain-text and HTML email, and supports a review-draft workflow for common
inline forwards.
Installation And Enablement
Install from the Hermes dashboard's Settings > Plugins > Install from Git
flow using JNevrly/hermes_email_pp, then enable email-pp when prompted.
Email++ requires Hermes Agent 0.20.5 or later in the 0.20 release line.
The dashboard installs the repository as a directory plugin. It stays disabled
until you explicitly enable it. Restart the gateway after installing or
changing configuration. hermes plugins list shows discovery and enablement,
and hermes gateway status shows the registered and connected platforms.
Select Channels > Email++ > Configure to enter all settings below. Vanilla
Hermes renders these as text/password inputs: enter true or false for the
two boolean settings, and always, forwarded, or never for quote mode.
Leave optional fields blank to use their documented runtime defaults. Restart
the gateway after saving. The card asks for Allow all users even though its
secure runtime default is false: enter false unless you deliberately want
to accept every non-automated sender.
EMAIL_PP_ADDRESS=agent@example.com
EMAIL_PP_PASSWORD=an-app-password
EMAIL_PP_IMAP_HOST=imap.example.com
EMAIL_PP_SMTP_HOST=smtp.example.com
EMAIL_PP_ALLOWED_USERS=operator@example.com
EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER=true
All four required variables must be non-empty before environment-driven
configuration enables Email++. The EMAIL_PP_* namespace is deliberately
isolated: built-in EMAIL_* credentials are never read.
Enable the Email++ gateway platform in the active profile's config.yaml when
it is not auto-enabled from the complete required environment configuration:
plugins:
enabled: [email-pp]
platforms:
email_pp:
enabled: true
For a mailbox shared with the built-in adapter, use only one adapter. Keep
the EMAIL_* credentials absent and disable email in the gateway
configuration before enabling Email++ with its EMAIL_PP_* credentials. Two
adapters polling the same mailbox can race, duplicate processing, or send
conflicting replies.
Settings
Environment variables take precedence over matching extra values in the
Email++ platform configuration.
| Setting | Required | Default | Meaning |
|---|---|---|---|
EMAIL_PP_ADDRESS |
Yes | - | Agent mailbox address and SMTP envelope identity. |
EMAIL_PP_PASSWORD |
Yes | - | IMAP/SMTP password or provider-issued app password. |
EMAIL_PP_IMAP_HOST |
Yes | - | IMAP server hostname. |
EMAIL_PP_SMTP_HOST |
Yes | - | SMTP server hostname. |
EMAIL_PP_IMAP_PORT |
No | 993 |
IMAP-over-TLS port. |
EMAIL_PP_SMTP_PORT |
No | 587 |
SMTP STARTTLS port; use 465 for implicit TLS. |
EMAIL_PP_SENDER_NAME |
No | Hermes Agent |
Display name used for outgoing replies; the mailbox address remains EMAIL_PP_ADDRESS. |
EMAIL_PP_POLL_INTERVAL |
No | 15 |
Inbox polling interval in seconds; values below one second are treated as one second. |
EMAIL_PP_MAILBOX |
No | INBOX |
Mailbox selected for polling. |
EMAIL_PP_ALLOWED_USERS |
No | empty | Comma-separated sender-address allowlist. Required unless EMAIL_PP_ALLOW_ALL_USERS is enabled. |
EMAIL_PP_ALLOW_ALL_USERS |
No | false |
Accept every non-automated sender. This also bypasses sender-authentication checks; do not use for an Internet-facing mailbox. |
EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER |
No | true |
Require a passing DMARC result in Authentication-Results for allowlisted senders. |
EMAIL_PP_AUTHSERV_ID |
No | empty | Optional authentication-service identifier that must prefix the trusted Authentication-Results header. |
EMAIL_PP_QUOTE_MODE |
No | always |
always quotes the source email, forwarded quotes only parsed forwards, and never omits visible quotes. |
EMAIL_PP_PROCESS_HISTORY_WINDOW |
No | 0 |
Unread mail recovery at a cold gateway start: 0 skips existing mail, -1 processes all unread mail, and a positive exact-second window processes only unread mail newer than that cutoff. |
EMAIL_PP_DELETE_PROCESSED |
No | false |
Delete authorized email only after Hermes completes successfully and its reply is accepted by SMTP. Requires IMAP UIDPLUS support. |
EMAIL_PP_MAX_OUTBOUND_ATTACHMENTS |
No | 10 |
Maximum files attached to one agent response. |
EMAIL_PP_MAX_OUTBOUND_TOTAL_BYTES |
No | 15728640 |
Maximum total raw attachment bytes before MIME encoding (15 MiB). |
EMAIL_PP_ALLOW_ALL_USERS accepts every non-automated sender and disables the
allowlist and sender-authentication checks. Leave it disabled for an
Internet-facing mailbox. The safer default,
EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER=true, remains enabled unless you
explicitly turn it off.
At a cold gateway start, Email++ normally ignores unread mail already present
in the selected mailbox. Set EMAIL_PP_PROCESS_HISTORY_WINDOW to -1 to
recover all unread mail, or to a positive number of seconds to recover only
recent unread mail. Invalid values and values below -1 prevent the adapter
from starting. Automatic reconnects in a running gateway retain their mailbox
state and process unread mail received during the outage.
Set EMAIL_PP_DELETE_PROCESSED=true only for a dedicated agent mailbox when
you want successful requests removed automatically. Email++ retains rejected,
malformed, cancelled, and failed requests. It records a successful response
before deleting and retries a failed mailbox deletion without rerunning Hermes.
Deletion requires IMAP UIDPLUS; Email++ never uses broad EXPUNGE, so it
cannot remove messages another mailbox client has marked for deletion.
Generated Attachments
When an email user asks to receive an asset, the agent must emit an explicit
MEDIA:/absolute/path/to/file directive after creating it. Email++ removes the
directive from the visible response and attaches every validated MEDIA: file
to the same MIME email as the response body. A Markdown link, local path, file
URL, or localhost URL is not a substitute when the asset itself was requested.
Local paths and local HTTP URLs remain valid informational references. The agent can attach an asset and also mention where it is stored when both are useful. Bare paths are never auto-attached by Email++, and remote image URLs remain links rather than being downloaded by the adapter.
Email++ sends final responses only after they are complete because SMTP cannot
edit an accepted message. Configure display.platforms.email_pp.streaming as
false and disable optional progress messages when one final email per turn is
required. Attachment delivery supports at most 10 files and 15 MiB of raw data
by default; configure EMAIL_PP_MAX_OUTBOUND_ATTACHMENTS and
EMAIL_PP_MAX_OUTBOUND_TOTAL_BYTES for providers with different limits.
If any requested attachment is missing, unsafe, unreadable, or exceeds a limit, Email++ sends a threaded failure notice without attaching a partial set of files. This plugin-only behavior relies on Hermes Agent 0.20.5 calling the adapter extraction hooks before its final text send; update compatibility tests before upgrading Hermes.
Email Behavior
Ordinary Replies
Each RFC thread is mapped to a private Hermes thread. Message-ID,
References, and In-Reply-To preserve continuity across replies and gateway
restarts. Replies include both text/plain and safe text/html alternatives.
The selected quote mode controls whether the original content is visibly
quoted; normal replies retain RFC In-Reply-To and References headers.
The adapter rejects automated mail, self-mail, malformed message-ID headers, senders outside the allowlist, and (by default) messages without a passing DMARC result. It only sends when Hermes supplies a known sender and reply route, preventing arbitrary outbound email addresses from being used as a delivery target.
Forwarded Review Drafts
Email++ recognizes English inline forwards in these forms:
---------- Forwarded message ---------
-----Original Message-----
________________________________
Outlook/O365 HTML forwards using an hr followed by the standard
divRplyFwdMsg header block are also supported.
Use a short task prompt before the boundary, followed by a complete forwarded
message with at least From, Subject, and a non-empty body. The first Hermes
response is sent only to the forwarder as a threaded reply with subject
Re: <forwarding subject>. It uses standard In-Reply-To and References
headers; when a forwarding wrapper has valid Outlook conversation headers, it
also preserves Thread-Topic and extends Thread-Index. It never sends to the
original correspondent; its visible quote contains only the original message,
never the wrapper task prompt. A reply to that message stays in the same Hermes
session and is sent as a normal revision.
Only English Gmail and Outlook inline forwards matching the boundaries above are supported. Attachments-as-forwards, localized client formatting, nested or ambiguous forwards, and forwards without the required headers/body are not parsed. A suspected forward that cannot be parsed receives a safe notice and creates no draft or agent task. This fail-closed behavior prevents wrapper text or untrusted forwarded content from being mistaken for an instruction.
Security And Data Handling
- Use a dedicated mailbox and a least-privilege provider app password, not a primary account password. Restrict IMAP/SMTP access to TLS-enabled endpoints.
- Store secrets in the Hermes profile
.envwith owner-only filesystem permissions or use the deployment platform's secret store. Never commit the.envfile or app password. - Use a small, explicit
EMAIL_PP_ALLOWED_USERSlist. LeaveEMAIL_PP_ALLOW_ALL_USERSunset, and keep authenticated-sender verification enabled. SetEMAIL_PP_AUTHSERV_IDwhen the receiving infrastructure has a known authentication-results service. - Treat sender authentication as a mailbox-side defense, not proof that email content is trustworthy. Forwarded message bodies are reference data and are explicitly separated from the authorized task prompt.
- Inbound attachments are written to temporary files and exposed to Hermes as media URLs; the adapter does not scan, size-limit, or delete them itself. Apply mailbox-provider malware controls, restrict tool access for the Email++ platform, and clean the host temporary directory according to local policy.
- Thread routing, delivery details, quote sources, and draft context are kept
locally at
~/.hermes/email_pp/threads.json(or the activeHERMES_HOME). The directory is mode0700and the state file is mode0600, but it is not encrypted. It is bounded to 500 threads and a 90-day retention period; back up or purge it according to the mailbox's data-retention policy.
Limitations
The adapter polls one IMAP mailbox and supports IMAP-over-TLS plus SMTP STARTTLS or implicit SMTP TLS. It does not provide mailbox synchronization, server-side draft storage, attachment malware scanning, arbitrary outbound mail, non-English forward parsing, or direct delivery to an original forwarded-message sender.
Development Validation
Run the complete release checks with an editable Hermes Agent v0.20.5 source checkout installed into the development environment:
$ uv sync --group dev
$ uv pip install -e /path/to/hermes-agent-v0.20.5
$ .venv/bin/ruff check .
$ .venv/bin/ruff format --check .
$ .venv/bin/mypy hermes_email_pp
$ .venv/bin/pytest --cov=hermes_email_pp tests/
$ uv build --out-dir dist --clear
The release workflow installs the official v0.20.5 source checkout in editable
mode before running the adapter and Git-install contract tests. Its wheel smoke
test confirms the email-pp entry point can load without replacing Hermes'
built-in email registration.
Releases
- Update the version in
pyproject.toml,plugin.yaml, andhermes_email_pp/__init__.py, then add the matching version section toCHANGELOG.md. - Regenerate the lockfile with
uv lock. Before tagging, verify it withuv lock --checkanduv sync --group dev --locked. - Commit and merge the release changes, then create and push a tag matching
vX.Y.Z. The tag must equalvfollowed by[project].version, for example package version0.2.0requires tagv0.2.0. - The Release workflow runs the full validation suite, builds the wheel and source distribution, publishes those exact artifacts to PyPI, and creates a GitHub Release with generated notes and the same artifacts. A failure at any stage prevents later stages from running.
The workflow has a manual workflow_dispatch tag input for recovering a valid
existing tag; it applies the same tag and version validation. Leave skip_pypi
disabled unless the exact version is already on PyPI. That recovery option lets
the workflow create a missing GitHub Release without attempting a duplicate
PyPI upload.
Before the first release, create a protected GitHub Actions environment named
pypi and configure PyPI Trusted Publishing for project hermes-email-pp with
GitHub owner JNevrly, repository hermes_email_pp, workflow
.github/workflows/release.yml (workflow filename release.yml), and
environment pypi. PyPI supports creating this as a pending publisher before
the project exists. No PyPI API token or repository secret is needed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_email_pp-0.5.0.tar.gz.
File metadata
- Download URL: hermes_email_pp-0.5.0.tar.gz
- Upload date:
- Size: 31.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e185b4f1d06526eea95a6d2b40399b6672e193d142a9ed3b0d03d5607562e64
|
|
| MD5 |
d32ea7e77702c10954ea6d81a1a6d8e5
|
|
| BLAKE2b-256 |
fcbe1aae1b0b3e02c9eab707df3655e57eaf0a9e8eb53b413732c381b104d453
|
Provenance
The following attestation bundles were made for hermes_email_pp-0.5.0.tar.gz:
Publisher:
release.yml on JNevrly/hermes_email_pp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hermes_email_pp-0.5.0.tar.gz -
Subject digest:
3e185b4f1d06526eea95a6d2b40399b6672e193d142a9ed3b0d03d5607562e64 - Sigstore transparency entry: 2711586948
- Sigstore integration time:
-
Permalink:
JNevrly/hermes_email_pp@1636160fa934e630caccf72fe68b6d4604266258 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/JNevrly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1636160fa934e630caccf72fe68b6d4604266258 -
Trigger Event:
push
-
Statement type:
File details
Details for the file hermes_email_pp-0.5.0-py3-none-any.whl.
File metadata
- Download URL: hermes_email_pp-0.5.0-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
988ebb9874b47999f89a87fea96726c8c58a5b7c8447dbdc708954c1920ee602
|
|
| MD5 |
a5f5d4d97a19d6c13ba280514cc03484
|
|
| BLAKE2b-256 |
bd97c830341b3ae5503f24e00f31291d5a035c02537365e07f61dbe06f9b05e6
|
Provenance
The following attestation bundles were made for hermes_email_pp-0.5.0-py3-none-any.whl:
Publisher:
release.yml on JNevrly/hermes_email_pp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hermes_email_pp-0.5.0-py3-none-any.whl -
Subject digest:
988ebb9874b47999f89a87fea96726c8c58a5b7c8447dbdc708954c1920ee602 - Sigstore transparency entry: 2711588166
- Sigstore integration time:
-
Permalink:
JNevrly/hermes_email_pp@1636160fa934e630caccf72fe68b6d4604266258 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/JNevrly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1636160fa934e630caccf72fe68b6d4604266258 -
Trigger Event:
push
-
Statement type: