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.
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/hermes_email_pp.svg)](https://pypi.python.org/pypi/hermes_email_pp)
25
+ [![CI](https://github.com/JNevrly/hermes_email_pp/actions/workflows/ci.yml/badge.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/hermes_email_pp.svg)](https://pypi.python.org/pypi/hermes_email_pp)
4
+ [![CI](https://github.com/JNevrly/hermes_email_pp/actions/workflows/ci.yml/badge.svg)](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.
@@ -0,0 +1,3 @@
1
+ """Hermes Email++ platform plugin."""
2
+
3
+ __version__ = "0.1.0"