hermes-email-pp 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hermes_email_pp-0.1.1/.gitignore +100 -0
- hermes_email_pp-0.1.1/LICENSE +22 -0
- hermes_email_pp-0.1.1/PKG-INFO +213 -0
- hermes_email_pp-0.1.1/README.md +192 -0
- hermes_email_pp-0.1.1/hermes_email_pp/__init__.py +3 -0
- hermes_email_pp-0.1.1/hermes_email_pp/adapter.py +627 -0
- hermes_email_pp-0.1.1/hermes_email_pp/config.py +74 -0
- hermes_email_pp-0.1.1/hermes_email_pp/forwarding.py +135 -0
- hermes_email_pp-0.1.1/hermes_email_pp/plugin.py +39 -0
- hermes_email_pp-0.1.1/hermes_email_pp/rendering.py +99 -0
- hermes_email_pp-0.1.1/hermes_email_pp/threading.py +263 -0
- hermes_email_pp-0.1.1/pyproject.toml +73 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
env/
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
|
|
28
|
+
# PyInstaller
|
|
29
|
+
# Usually these files are written by a python script from a template
|
|
30
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
.hypothesis/
|
|
48
|
+
.pytest_cache/
|
|
49
|
+
|
|
50
|
+
# Translations
|
|
51
|
+
*.mo
|
|
52
|
+
*.pot
|
|
53
|
+
|
|
54
|
+
# Django stuff:
|
|
55
|
+
*.log
|
|
56
|
+
local_settings.py
|
|
57
|
+
|
|
58
|
+
# Flask stuff:
|
|
59
|
+
instance/
|
|
60
|
+
.webassets-cache
|
|
61
|
+
|
|
62
|
+
# Scrapy stuff:
|
|
63
|
+
.scrapy
|
|
64
|
+
|
|
65
|
+
# PyBuilder
|
|
66
|
+
target/
|
|
67
|
+
|
|
68
|
+
# Jupyter Notebook
|
|
69
|
+
.ipynb_checkpoints
|
|
70
|
+
|
|
71
|
+
# pyenv
|
|
72
|
+
#.python-version
|
|
73
|
+
|
|
74
|
+
# celery beat schedule file
|
|
75
|
+
celerybeat-schedule
|
|
76
|
+
|
|
77
|
+
# SageMath parsed files
|
|
78
|
+
*.sage.py
|
|
79
|
+
|
|
80
|
+
# dotenv
|
|
81
|
+
.env
|
|
82
|
+
|
|
83
|
+
# virtualenv
|
|
84
|
+
.venv
|
|
85
|
+
venv/
|
|
86
|
+
ENV/
|
|
87
|
+
|
|
88
|
+
# Spyder project settings
|
|
89
|
+
.spyderproject
|
|
90
|
+
.spyproject
|
|
91
|
+
|
|
92
|
+
# Rope project settings
|
|
93
|
+
.ropeproject
|
|
94
|
+
|
|
95
|
+
# mypy
|
|
96
|
+
.mypy_cache/
|
|
97
|
+
alabaster
|
|
98
|
+
|
|
99
|
+
.vscode/
|
|
100
|
+
.idea/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Josef Nevrly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: hermes-email-pp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Better email adapter for Hermes Agent.
|
|
5
|
+
Project-URL: Homepage, https://github.com/JNevrly/hermes_email_pp
|
|
6
|
+
Project-URL: Repository, https://github.com/JNevrly/hermes_email_pp
|
|
7
|
+
Author-email: Josef Nevrly <josef.nevrly@gmail.com>
|
|
8
|
+
License: MIT license
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Requires-Python: <3.14,>=3.11
|
|
18
|
+
Requires-Dist: hermes-agent<0.20,>=0.19
|
|
19
|
+
Requires-Dist: markdown<4.0,>=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Hermes Email++
|
|
23
|
+
|
|
24
|
+
[](https://pypi.python.org/pypi/hermes_email_pp)
|
|
25
|
+
[](https://github.com/JNevrly/hermes_email_pp/actions/workflows/ci.yml)
|
|
26
|
+
|
|
27
|
+
Hermes Email++ is a third-party IMAP/SMTP platform adapter for Hermes Agent.
|
|
28
|
+
It is registered as `email_pp`, independently of Hermes' built-in `email`
|
|
29
|
+
platform. It routes each RFC email thread to its own Hermes session, sends
|
|
30
|
+
plain-text and HTML email, and supports a review-draft workflow for common
|
|
31
|
+
inline forwards.
|
|
32
|
+
|
|
33
|
+
## Installation And Enablement
|
|
34
|
+
|
|
35
|
+
Install this package into the same Python environment as a supported Hermes
|
|
36
|
+
Agent 0.19 release:
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ python -m pip install hermes-email-pp
|
|
40
|
+
$ hermes plugins enable email-pp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Pip-installed plugins are discovered through the `hermes_agent.plugins` entry
|
|
44
|
+
point. Enabling `email-pp` explicitly permits this third-party code to load.
|
|
45
|
+
Restart the gateway after installing or changing configuration. `hermes plugins
|
|
46
|
+
list` shows discovery and enablement, and `hermes gateway status` shows the
|
|
47
|
+
registered and connected platforms.
|
|
48
|
+
|
|
49
|
+
Put the credentials in the active Hermes profile's `~/.hermes/.env` (or the
|
|
50
|
+
profile-specific equivalent):
|
|
51
|
+
|
|
52
|
+
```dotenv
|
|
53
|
+
EMAIL_PP_ADDRESS=agent@example.com
|
|
54
|
+
EMAIL_PP_PASSWORD=an-app-password
|
|
55
|
+
EMAIL_PP_IMAP_HOST=imap.example.com
|
|
56
|
+
EMAIL_PP_SMTP_HOST=smtp.example.com
|
|
57
|
+
EMAIL_PP_ALLOWED_USERS=operator@example.com
|
|
58
|
+
EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER=true
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
All four required variables must be non-empty before environment-driven
|
|
62
|
+
configuration enables Email++. The `EMAIL_PP_*` namespace is deliberately
|
|
63
|
+
isolated: built-in `EMAIL_*` credentials are never read.
|
|
64
|
+
|
|
65
|
+
Enable the Email++ gateway platform in `~/.hermes/config.yaml` when it is not
|
|
66
|
+
auto-enabled from the complete required environment configuration:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
gateway:
|
|
70
|
+
platforms:
|
|
71
|
+
email_pp:
|
|
72
|
+
enabled: true
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For a mailbox shared with the built-in adapter, use **only one** adapter. Keep
|
|
76
|
+
the `EMAIL_*` credentials absent and disable `email` in the gateway
|
|
77
|
+
configuration before enabling Email++ with its `EMAIL_PP_*` credentials. Two
|
|
78
|
+
adapters polling the same mailbox can race, duplicate processing, or send
|
|
79
|
+
conflicting replies.
|
|
80
|
+
|
|
81
|
+
### Settings
|
|
82
|
+
|
|
83
|
+
Environment variables take precedence over matching `extra` values in the
|
|
84
|
+
Email++ platform configuration.
|
|
85
|
+
|
|
86
|
+
| Setting | Required | Default | Meaning |
|
|
87
|
+
| --- | --- | --- | --- |
|
|
88
|
+
| `EMAIL_PP_ADDRESS` | Yes | - | Agent mailbox address and SMTP envelope identity. |
|
|
89
|
+
| `EMAIL_PP_PASSWORD` | Yes | - | IMAP/SMTP password or provider-issued app password. |
|
|
90
|
+
| `EMAIL_PP_IMAP_HOST` | Yes | - | IMAP server hostname. |
|
|
91
|
+
| `EMAIL_PP_SMTP_HOST` | Yes | - | SMTP server hostname. |
|
|
92
|
+
| `EMAIL_PP_IMAP_PORT` | No | `993` | IMAP-over-TLS port. |
|
|
93
|
+
| `EMAIL_PP_SMTP_PORT` | No | `587` | SMTP STARTTLS port; use `465` for implicit TLS. |
|
|
94
|
+
| `EMAIL_PP_POLL_INTERVAL` | No | `15` | Inbox polling interval in seconds; values below one second are treated as one second. |
|
|
95
|
+
| `EMAIL_PP_MAILBOX` | No | `INBOX` | Mailbox selected for polling. |
|
|
96
|
+
| `EMAIL_PP_ALLOWED_USERS` | No | empty | Comma-separated sender-address allowlist. Required unless `EMAIL_PP_ALLOW_ALL_USERS` is enabled. |
|
|
97
|
+
| `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. |
|
|
98
|
+
| `EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER` | No | `true` | Require a passing DMARC result in `Authentication-Results` for allowlisted senders. |
|
|
99
|
+
| `EMAIL_PP_AUTHSERV_ID` | No | empty | Optional authentication-service identifier that must prefix the trusted `Authentication-Results` header. |
|
|
100
|
+
| `EMAIL_PP_QUOTE_MODE` | No | `always` | `always` quotes the source email, `forwarded` quotes only parsed forwards, and `never` omits visible quotes. |
|
|
101
|
+
|
|
102
|
+
## Email Behavior
|
|
103
|
+
|
|
104
|
+
### Ordinary Replies
|
|
105
|
+
|
|
106
|
+
Each RFC thread is mapped to a private Hermes thread. `Message-ID`,
|
|
107
|
+
`References`, and `In-Reply-To` preserve continuity across replies and gateway
|
|
108
|
+
restarts. Replies include both `text/plain` and safe `text/html` alternatives.
|
|
109
|
+
The selected quote mode controls whether the original content is visibly
|
|
110
|
+
quoted; normal replies retain RFC `In-Reply-To` and `References` headers.
|
|
111
|
+
|
|
112
|
+
The adapter rejects automated mail, self-mail, malformed message-ID headers,
|
|
113
|
+
senders outside the allowlist, and (by default) messages without a passing
|
|
114
|
+
DMARC result. It only sends when Hermes supplies a known sender and reply
|
|
115
|
+
route, preventing arbitrary outbound email addresses from being used as a
|
|
116
|
+
delivery target.
|
|
117
|
+
|
|
118
|
+
### Forwarded Review Drafts
|
|
119
|
+
|
|
120
|
+
Email++ recognizes English inline forwards in these forms:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
---------- Forwarded message ---------
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
-----Original Message-----
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use a short task prompt before the boundary, followed by a complete forwarded
|
|
131
|
+
message with at least `From`, `Subject`, and a non-empty body. The first Hermes
|
|
132
|
+
response is sent to the forwarder as a separate email with subject
|
|
133
|
+
`Draft: Re: <original subject>`; it does not reply to the original
|
|
134
|
+
correspondent and has no original-thread reply headers. The visible quote
|
|
135
|
+
contains only the original message, never the wrapper task prompt. A reply to
|
|
136
|
+
that draft stays in the same Hermes session and is sent as a normal revision.
|
|
137
|
+
|
|
138
|
+
Only English Gmail and Outlook inline forwards matching the boundaries above
|
|
139
|
+
are supported. Attachments-as-forwards, localized client formatting, nested or
|
|
140
|
+
ambiguous forwards, and forwards without the required headers/body are not
|
|
141
|
+
parsed. A suspected forward that cannot be parsed receives a safe notice and
|
|
142
|
+
creates no draft or agent task. This fail-closed behavior prevents wrapper text
|
|
143
|
+
or untrusted forwarded content from being mistaken for an instruction.
|
|
144
|
+
|
|
145
|
+
## Security And Data Handling
|
|
146
|
+
|
|
147
|
+
- Use a dedicated mailbox and a least-privilege provider app password, not a
|
|
148
|
+
primary account password. Restrict IMAP/SMTP access to TLS-enabled endpoints.
|
|
149
|
+
- Store secrets in the Hermes profile `.env` with owner-only filesystem
|
|
150
|
+
permissions or use the deployment platform's secret store. Never commit the
|
|
151
|
+
`.env` file or app password.
|
|
152
|
+
- Use a small, explicit `EMAIL_PP_ALLOWED_USERS` list. Leave
|
|
153
|
+
`EMAIL_PP_ALLOW_ALL_USERS` unset, and keep authenticated-sender verification
|
|
154
|
+
enabled. Set `EMAIL_PP_AUTHSERV_ID` when the receiving infrastructure has a
|
|
155
|
+
known authentication-results service.
|
|
156
|
+
- Treat sender authentication as a mailbox-side defense, not proof that email
|
|
157
|
+
content is trustworthy. Forwarded message bodies are reference data and are
|
|
158
|
+
explicitly separated from the authorized task prompt.
|
|
159
|
+
- Inbound attachments are written to temporary files and exposed to Hermes as
|
|
160
|
+
media URLs; the adapter does not scan, size-limit, or delete them itself.
|
|
161
|
+
Apply mailbox-provider malware controls, restrict tool access for the Email++
|
|
162
|
+
platform, and clean the host temporary directory according to local policy.
|
|
163
|
+
- Thread routing, delivery details, quote sources, and draft context are kept
|
|
164
|
+
locally at `~/.hermes/email_pp/threads.json` (or the active `HERMES_HOME`).
|
|
165
|
+
The directory is mode `0700` and the state file is mode `0600`, but it is not
|
|
166
|
+
encrypted. It is bounded to 500 threads and a 90-day retention period; back
|
|
167
|
+
up or purge it according to the mailbox's data-retention policy.
|
|
168
|
+
|
|
169
|
+
## Limitations
|
|
170
|
+
|
|
171
|
+
The adapter polls one IMAP mailbox and supports IMAP-over-TLS plus SMTP
|
|
172
|
+
STARTTLS or implicit SMTP TLS. It does not provide mailbox synchronization,
|
|
173
|
+
server-side draft storage, attachment malware scanning, arbitrary outbound
|
|
174
|
+
mail, non-English forward parsing, or direct delivery to an original
|
|
175
|
+
forwarded-message sender.
|
|
176
|
+
|
|
177
|
+
## Development Validation
|
|
178
|
+
|
|
179
|
+
Run the complete release checks from a clean checkout:
|
|
180
|
+
|
|
181
|
+
```console
|
|
182
|
+
$ uv run ruff check .
|
|
183
|
+
$ uv run ruff format --check .
|
|
184
|
+
$ uv run mypy hermes_email_pp
|
|
185
|
+
$ uv run pytest --cov=hermes_email_pp tests/
|
|
186
|
+
$ uv build --out-dir dist --clear
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The release smoke test installs the built wheel with a supported Hermes Agent
|
|
190
|
+
0.19 release, then confirms the `email-pp` entry point loads without replacing
|
|
191
|
+
the built-in `email` registration.
|
|
192
|
+
|
|
193
|
+
## Releases
|
|
194
|
+
|
|
195
|
+
1. Update `[project].version` in `pyproject.toml` and add the matching version
|
|
196
|
+
section to `CHANGELOG.md`.
|
|
197
|
+
2. Commit and merge the release changes, then create and push a tag matching
|
|
198
|
+
`vX.Y.Z`. The tag must equal `v` followed by `[project].version`, for example
|
|
199
|
+
package version `0.2.0` requires tag `v0.2.0`.
|
|
200
|
+
3. The Release workflow runs the full validation suite, builds the wheel and
|
|
201
|
+
source distribution, publishes those exact artifacts to PyPI, and creates a
|
|
202
|
+
GitHub Release with generated notes and the same artifacts. A failure at any
|
|
203
|
+
stage prevents later stages from running.
|
|
204
|
+
|
|
205
|
+
The workflow has a manual `workflow_dispatch` tag input for recovering a valid
|
|
206
|
+
existing tag; it applies the same tag and version validation.
|
|
207
|
+
|
|
208
|
+
Before the first release, create a protected GitHub Actions environment named
|
|
209
|
+
`pypi` and configure PyPI Trusted Publishing for project `hermes-email-pp` with
|
|
210
|
+
GitHub owner `JNevrly`, repository `hermes_email_pp`, workflow
|
|
211
|
+
`.github/workflows/release.yml` (workflow filename `release.yml`), and
|
|
212
|
+
environment `pypi`. PyPI supports creating this as a pending publisher before
|
|
213
|
+
the project exists. No PyPI API token or repository secret is needed.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Hermes Email++
|
|
2
|
+
|
|
3
|
+
[](https://pypi.python.org/pypi/hermes_email_pp)
|
|
4
|
+
[](https://github.com/JNevrly/hermes_email_pp/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
Hermes Email++ is a third-party IMAP/SMTP platform adapter for Hermes Agent.
|
|
7
|
+
It is registered as `email_pp`, independently of Hermes' built-in `email`
|
|
8
|
+
platform. It routes each RFC email thread to its own Hermes session, sends
|
|
9
|
+
plain-text and HTML email, and supports a review-draft workflow for common
|
|
10
|
+
inline forwards.
|
|
11
|
+
|
|
12
|
+
## Installation And Enablement
|
|
13
|
+
|
|
14
|
+
Install this package into the same Python environment as a supported Hermes
|
|
15
|
+
Agent 0.19 release:
|
|
16
|
+
|
|
17
|
+
```console
|
|
18
|
+
$ python -m pip install hermes-email-pp
|
|
19
|
+
$ hermes plugins enable email-pp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Pip-installed plugins are discovered through the `hermes_agent.plugins` entry
|
|
23
|
+
point. Enabling `email-pp` explicitly permits this third-party code to load.
|
|
24
|
+
Restart the gateway after installing or changing configuration. `hermes plugins
|
|
25
|
+
list` shows discovery and enablement, and `hermes gateway status` shows the
|
|
26
|
+
registered and connected platforms.
|
|
27
|
+
|
|
28
|
+
Put the credentials in the active Hermes profile's `~/.hermes/.env` (or the
|
|
29
|
+
profile-specific equivalent):
|
|
30
|
+
|
|
31
|
+
```dotenv
|
|
32
|
+
EMAIL_PP_ADDRESS=agent@example.com
|
|
33
|
+
EMAIL_PP_PASSWORD=an-app-password
|
|
34
|
+
EMAIL_PP_IMAP_HOST=imap.example.com
|
|
35
|
+
EMAIL_PP_SMTP_HOST=smtp.example.com
|
|
36
|
+
EMAIL_PP_ALLOWED_USERS=operator@example.com
|
|
37
|
+
EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER=true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
All four required variables must be non-empty before environment-driven
|
|
41
|
+
configuration enables Email++. The `EMAIL_PP_*` namespace is deliberately
|
|
42
|
+
isolated: built-in `EMAIL_*` credentials are never read.
|
|
43
|
+
|
|
44
|
+
Enable the Email++ gateway platform in `~/.hermes/config.yaml` when it is not
|
|
45
|
+
auto-enabled from the complete required environment configuration:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
gateway:
|
|
49
|
+
platforms:
|
|
50
|
+
email_pp:
|
|
51
|
+
enabled: true
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For a mailbox shared with the built-in adapter, use **only one** adapter. Keep
|
|
55
|
+
the `EMAIL_*` credentials absent and disable `email` in the gateway
|
|
56
|
+
configuration before enabling Email++ with its `EMAIL_PP_*` credentials. Two
|
|
57
|
+
adapters polling the same mailbox can race, duplicate processing, or send
|
|
58
|
+
conflicting replies.
|
|
59
|
+
|
|
60
|
+
### Settings
|
|
61
|
+
|
|
62
|
+
Environment variables take precedence over matching `extra` values in the
|
|
63
|
+
Email++ platform configuration.
|
|
64
|
+
|
|
65
|
+
| Setting | Required | Default | Meaning |
|
|
66
|
+
| --- | --- | --- | --- |
|
|
67
|
+
| `EMAIL_PP_ADDRESS` | Yes | - | Agent mailbox address and SMTP envelope identity. |
|
|
68
|
+
| `EMAIL_PP_PASSWORD` | Yes | - | IMAP/SMTP password or provider-issued app password. |
|
|
69
|
+
| `EMAIL_PP_IMAP_HOST` | Yes | - | IMAP server hostname. |
|
|
70
|
+
| `EMAIL_PP_SMTP_HOST` | Yes | - | SMTP server hostname. |
|
|
71
|
+
| `EMAIL_PP_IMAP_PORT` | No | `993` | IMAP-over-TLS port. |
|
|
72
|
+
| `EMAIL_PP_SMTP_PORT` | No | `587` | SMTP STARTTLS port; use `465` for implicit TLS. |
|
|
73
|
+
| `EMAIL_PP_POLL_INTERVAL` | No | `15` | Inbox polling interval in seconds; values below one second are treated as one second. |
|
|
74
|
+
| `EMAIL_PP_MAILBOX` | No | `INBOX` | Mailbox selected for polling. |
|
|
75
|
+
| `EMAIL_PP_ALLOWED_USERS` | No | empty | Comma-separated sender-address allowlist. Required unless `EMAIL_PP_ALLOW_ALL_USERS` is enabled. |
|
|
76
|
+
| `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. |
|
|
77
|
+
| `EMAIL_PP_REQUIRE_AUTHENTICATED_SENDER` | No | `true` | Require a passing DMARC result in `Authentication-Results` for allowlisted senders. |
|
|
78
|
+
| `EMAIL_PP_AUTHSERV_ID` | No | empty | Optional authentication-service identifier that must prefix the trusted `Authentication-Results` header. |
|
|
79
|
+
| `EMAIL_PP_QUOTE_MODE` | No | `always` | `always` quotes the source email, `forwarded` quotes only parsed forwards, and `never` omits visible quotes. |
|
|
80
|
+
|
|
81
|
+
## Email Behavior
|
|
82
|
+
|
|
83
|
+
### Ordinary Replies
|
|
84
|
+
|
|
85
|
+
Each RFC thread is mapped to a private Hermes thread. `Message-ID`,
|
|
86
|
+
`References`, and `In-Reply-To` preserve continuity across replies and gateway
|
|
87
|
+
restarts. Replies include both `text/plain` and safe `text/html` alternatives.
|
|
88
|
+
The selected quote mode controls whether the original content is visibly
|
|
89
|
+
quoted; normal replies retain RFC `In-Reply-To` and `References` headers.
|
|
90
|
+
|
|
91
|
+
The adapter rejects automated mail, self-mail, malformed message-ID headers,
|
|
92
|
+
senders outside the allowlist, and (by default) messages without a passing
|
|
93
|
+
DMARC result. It only sends when Hermes supplies a known sender and reply
|
|
94
|
+
route, preventing arbitrary outbound email addresses from being used as a
|
|
95
|
+
delivery target.
|
|
96
|
+
|
|
97
|
+
### Forwarded Review Drafts
|
|
98
|
+
|
|
99
|
+
Email++ recognizes English inline forwards in these forms:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
---------- Forwarded message ---------
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
-----Original Message-----
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use a short task prompt before the boundary, followed by a complete forwarded
|
|
110
|
+
message with at least `From`, `Subject`, and a non-empty body. The first Hermes
|
|
111
|
+
response is sent to the forwarder as a separate email with subject
|
|
112
|
+
`Draft: Re: <original subject>`; it does not reply to the original
|
|
113
|
+
correspondent and has no original-thread reply headers. The visible quote
|
|
114
|
+
contains only the original message, never the wrapper task prompt. A reply to
|
|
115
|
+
that draft stays in the same Hermes session and is sent as a normal revision.
|
|
116
|
+
|
|
117
|
+
Only English Gmail and Outlook inline forwards matching the boundaries above
|
|
118
|
+
are supported. Attachments-as-forwards, localized client formatting, nested or
|
|
119
|
+
ambiguous forwards, and forwards without the required headers/body are not
|
|
120
|
+
parsed. A suspected forward that cannot be parsed receives a safe notice and
|
|
121
|
+
creates no draft or agent task. This fail-closed behavior prevents wrapper text
|
|
122
|
+
or untrusted forwarded content from being mistaken for an instruction.
|
|
123
|
+
|
|
124
|
+
## Security And Data Handling
|
|
125
|
+
|
|
126
|
+
- Use a dedicated mailbox and a least-privilege provider app password, not a
|
|
127
|
+
primary account password. Restrict IMAP/SMTP access to TLS-enabled endpoints.
|
|
128
|
+
- Store secrets in the Hermes profile `.env` with owner-only filesystem
|
|
129
|
+
permissions or use the deployment platform's secret store. Never commit the
|
|
130
|
+
`.env` file or app password.
|
|
131
|
+
- Use a small, explicit `EMAIL_PP_ALLOWED_USERS` list. Leave
|
|
132
|
+
`EMAIL_PP_ALLOW_ALL_USERS` unset, and keep authenticated-sender verification
|
|
133
|
+
enabled. Set `EMAIL_PP_AUTHSERV_ID` when the receiving infrastructure has a
|
|
134
|
+
known authentication-results service.
|
|
135
|
+
- Treat sender authentication as a mailbox-side defense, not proof that email
|
|
136
|
+
content is trustworthy. Forwarded message bodies are reference data and are
|
|
137
|
+
explicitly separated from the authorized task prompt.
|
|
138
|
+
- Inbound attachments are written to temporary files and exposed to Hermes as
|
|
139
|
+
media URLs; the adapter does not scan, size-limit, or delete them itself.
|
|
140
|
+
Apply mailbox-provider malware controls, restrict tool access for the Email++
|
|
141
|
+
platform, and clean the host temporary directory according to local policy.
|
|
142
|
+
- Thread routing, delivery details, quote sources, and draft context are kept
|
|
143
|
+
locally at `~/.hermes/email_pp/threads.json` (or the active `HERMES_HOME`).
|
|
144
|
+
The directory is mode `0700` and the state file is mode `0600`, but it is not
|
|
145
|
+
encrypted. It is bounded to 500 threads and a 90-day retention period; back
|
|
146
|
+
up or purge it according to the mailbox's data-retention policy.
|
|
147
|
+
|
|
148
|
+
## Limitations
|
|
149
|
+
|
|
150
|
+
The adapter polls one IMAP mailbox and supports IMAP-over-TLS plus SMTP
|
|
151
|
+
STARTTLS or implicit SMTP TLS. It does not provide mailbox synchronization,
|
|
152
|
+
server-side draft storage, attachment malware scanning, arbitrary outbound
|
|
153
|
+
mail, non-English forward parsing, or direct delivery to an original
|
|
154
|
+
forwarded-message sender.
|
|
155
|
+
|
|
156
|
+
## Development Validation
|
|
157
|
+
|
|
158
|
+
Run the complete release checks from a clean checkout:
|
|
159
|
+
|
|
160
|
+
```console
|
|
161
|
+
$ uv run ruff check .
|
|
162
|
+
$ uv run ruff format --check .
|
|
163
|
+
$ uv run mypy hermes_email_pp
|
|
164
|
+
$ uv run pytest --cov=hermes_email_pp tests/
|
|
165
|
+
$ uv build --out-dir dist --clear
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The release smoke test installs the built wheel with a supported Hermes Agent
|
|
169
|
+
0.19 release, then confirms the `email-pp` entry point loads without replacing
|
|
170
|
+
the built-in `email` registration.
|
|
171
|
+
|
|
172
|
+
## Releases
|
|
173
|
+
|
|
174
|
+
1. Update `[project].version` in `pyproject.toml` and add the matching version
|
|
175
|
+
section to `CHANGELOG.md`.
|
|
176
|
+
2. Commit and merge the release changes, then create and push a tag matching
|
|
177
|
+
`vX.Y.Z`. The tag must equal `v` followed by `[project].version`, for example
|
|
178
|
+
package version `0.2.0` requires tag `v0.2.0`.
|
|
179
|
+
3. The Release workflow runs the full validation suite, builds the wheel and
|
|
180
|
+
source distribution, publishes those exact artifacts to PyPI, and creates a
|
|
181
|
+
GitHub Release with generated notes and the same artifacts. A failure at any
|
|
182
|
+
stage prevents later stages from running.
|
|
183
|
+
|
|
184
|
+
The workflow has a manual `workflow_dispatch` tag input for recovering a valid
|
|
185
|
+
existing tag; it applies the same tag and version validation.
|
|
186
|
+
|
|
187
|
+
Before the first release, create a protected GitHub Actions environment named
|
|
188
|
+
`pypi` and configure PyPI Trusted Publishing for project `hermes-email-pp` with
|
|
189
|
+
GitHub owner `JNevrly`, repository `hermes_email_pp`, workflow
|
|
190
|
+
`.github/workflows/release.yml` (workflow filename `release.yml`), and
|
|
191
|
+
environment `pypi`. PyPI supports creating this as a pending publisher before
|
|
192
|
+
the project exists. No PyPI API token or repository secret is needed.
|