feat(email-smtp): create Email (SMTP) send injector (#23) - #289
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Email (SMTP) injector module to the OpenAEV injectors monorepo, including its runtime entrypoint, SMTP client, configuration schema, containerization, and initial tests/docs.
Changes:
- Added a new
email/injector package (email_injector) with contract definition, config loader, and SMTP send implementation. - Added Docker build/run assets (
Dockerfile,docker-compose.yml) plus sample configs (config.yml.sample,.env.sample). - Added initial tests and a README describing SMTP-specific configuration.
Reviewed changes
Copilot reviewed 16 out of 22 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| email/test/test_email_helper.py | Adds a unit test for payload building. |
| email/test/test_email_client.py | Adds unit tests for SMTP send behavior (mocked). |
| email/test/init.py | Initializes the test package. |
| email/README.md | Documents injector purpose and SMTP config keys. |
| email/pyproject.toml | Defines the injector package + dependencies/dev tooling. |
| email/manifest-metadata.json | Adds catalog/manager metadata for the injector image. |
| email/email-injector-findings.md | Research/notes for future scope (signatures/headers/templates). |
| email/email_injector/openaev_email.py | Injector entrypoint wiring config/contracts to SMTP send execution. |
| email/email_injector/helpers/email_helper.py | Implements payload builder from inject content. |
| email/email_injector/helpers/init.py | Package init for helpers. |
| email/email_injector/contracts_email.py | Defines the Email inject contract (from/to/subject/body). |
| email/email_injector/configuration/injector_config_override.py | Declares injector config schema including SMTP settings. |
| email/email_injector/configuration/config_loader.py | Flattens OpenAEV/injector/SMTP config into daemon config. |
| email/email_injector/configuration/init.py | Package init for configuration. |
| email/email_injector/client/email_client.py | Implements SMTP send via smtplib. |
| email/email_injector/client/init.py | Package init for client. |
| email/email_injector/init.py | Package init for email injector. |
| email/Dockerfile | Builds a container image for the email injector via Poetry. |
| email/docker-compose.yml | Provides compose template to run the injector container. |
| email/config.yml.sample | Provides a sample YAML config for manual deployment. |
| email/.env.sample | Provides a sample env file for docker-compose deployment. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #289 +/- ##
==========================================
+ Coverage 77.38% 77.82% +0.43%
==========================================
Files 129 143 +14
Lines 5307 5524 +217
==========================================
+ Hits 4107 4299 +192
- Misses 1200 1225 +25
📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
8818686 to
058b896
Compare
63aacf9 to
058b896
Compare
There was a problem hiding this comment.
Thank you for your work!
I performed several tests and successfully received the test emails. However, I identified a few points that could be improved:
- It seems (to be confirmed) that the Dockerfile-based version is not working. This may be specific to my environment, but this point should be investigated.
- The
README.mdis currently very concise. Several important pieces of information are missing, and it would benefit from being enriched to better align with our conventions. - As mentioned by Copilot, the
tenant_idis missing from all available configurations. - A few remarks regarding conventions:
- The default value for
log_levelshould beerrorrather thaninfo. - The folder should be named
tests(plural).
- The default value for
- Please note that the
email-injector-findingsfile appears to be a development memo. (be deleted) - The PNG logo has a transparent background, which may cause display issues on the frontend, especially with dark themes.
- Regarding the
log_levelfield, it would be preferable to use a PydanticLiteralin order to explicitly define the accepted log levels (debug,info,warning,error,critical) and ensure stricter configuration validation.
PS: One remark regarding the architecture: this is the point that sad me the most. The implementation was not based on the latest injector created, which is currently the reference for the injector guidelines (see CONTRIBUTING.md).
Future improvements
- Currently, the configuration only supports
user/passwordauthentication. Would it be possible to consider adding support for other authentication methods, such as OAuth2 or certificate-based authentication? - At the moment, only the MIME format
text/plainis supported. Supportingtext/htmlcould be an interesting improvement. Is limiting the support to plain text a deliberate design choice, or is HTML support something that could be considered in the future? - The feedback section
Trace messagecould be improved. It would benefit from more detailed information?
SamuelHassine
left a comment
There was a problem hiding this comment.
Reviewed the full injector code (client, contracts, config loaders, helpers, Dockerfile, samples, tests), applied fixes directly on the branch and drove CI back to green. The SMTP client now uses a 30s timeout with a context-managed connection, the optional tenant id is propagated like in the other injectors, the README follows the standardized structure and the internal research notes were removed. All review threads are addressed and resolved, all checks pass.
|
Review and fix pass summary:
Remaining non-CI blockers for the maintainers:
|
SamuelHassine
left a comment
There was a problem hiding this comment.
Approving after an independent senior review of the full changed files (not just the diff).
SMTP handling is solid: the connection uses a 30s timeout inside a context manager, so it is always closed on every failure path (STARTTLS / login / send); credentials are never logged; Bcc is kept out of the headers but included in the envelope recipient list; comma-separated Cc/Bcc parsing strips and drops empties; attachment download from OpenAEV is validated (status 200, non-null, binary). CRLF header injection in from/to/cc/subject is blocked by the standard-library email policy (it raises on embedded headers), which the client turns into a clean failure result - now covered by tests.
Two changes pushed in this pass (chore commit):
- Added email/.dockerignore (config.yml, .env, pycache). It was the only buildable injector missing one, and the Dockerfile uses
ADD . ${installdir}, so a local config.yml with real SMTP credentials or bytecode caches could otherwise be baked into the image. - Added unit tests locking the CRLF header-injection defense for the recipient and subject fields.
Leaving the "do not merge" label in place: this is the MVP1 base of a stack (#299 and #300 build on it) and merge order is handled by maintainers. Approval only - not merging.
|
Review-and-fix pass summary (independent senior review + fixes):
Note: the "do not merge" label is intentional and left in place - this is the MVP1 base of a stack (#299, #300) and merge order is a maintainer decision. Approved only, not merged. |
|
Review and fix completed:
|
873d024 to
8cb30f8
Compare
Megafredo
left a comment
There was a problem hiding this comment.
Nice work! Thanks for the fixes and the architecture! Everything looks good to me.
…23) - add a 30s timeout to the SMTP connection and use a context manager so the connection is always closed, even when STARTTLS/login/send fails - propagate the optional OpenAEV tenant id (openaev_tenant_id) through the config loader and add OPENAEV_TENANT_ID to .env.sample, docker-compose.yml and config.yml.sample, matching other injectors - extend the README to the standardized injector README structure (requirements, configuration tables, deployment, contract fields, behavior, debugging) - remove internal research notes (email-injector-findings.md) from the injector directory - update tests for the context-managed SMTP client and add a test asserting the connection is closed when send fails
…#23) - Add email/.dockerignore (config.yml, .env, __pycache__), matching every other buildable injector. Without it, `ADD . ${installdir}` in the Dockerfile could bake a local config.yml (real SMTP credentials) or bytecode caches into the published image. - Add unit tests asserting the SMTP client fails closed on CRLF header injection in the recipient and subject fields (the stdlib email policy raises when a header value contains an embedded header), so this security property is regression-protected.
- rename dir email/ -> email-smtp/ and package email_injector -> email_smtp_injector - rename entrypoint openaev_email.py -> openaev_email_smtp.py - change injector type openaev_email -> openaev_email_smtp and image to openaev/injector-email-smtp - rename contract and injector display name to "Email (SMTP)" - update manifest, docker-compose, .env/config samples and README Distinguishes the SMTP transport from the new Email (Microsoft 365) and Email (Google Workspace) API-based email injectors.
…tandards (#23) - convert test_email_client.py and test_email_helper.py from pytest-style functions to unittest.TestCase classes so they run under both python -m unittest (CircleCI runner) and pytest (GitHub Actions runner), matching the style of the other injectors (slack, teams, stratus, ai-redteam, injector_common) - add the missing .gitignore (config.yml, .env, __pycache__), previously the only injector without one - fix the stale contract label in the README ("Email - Craft email" -> "Email (SMTP) - Craft email") left over from the rename - drop the redundant mandatory=False inside ContractBuilder.optional(), which already sets it - align pyoaev pin to ==2.260717.0 and bump the injector version to 2.260719.0, matching the repo-wide versions after merging main
Validate incomplete SMTP credentials and attachment identifiers, correct the tenant configuration sample, and cover injector execution callbacks. Co-authored-by: Mariot Tsitoara <mariot.tsitoara@filigran.io>
8cb30f8 to
ff55eb9
Compare



Proposed changes
Testing Instructions
docker run --rm -p 1025:1025 -p 8025:8025 axllent/mailpit.config.ymlfromconfig.yml.sampleor.envfrom.env.sample, then runpoetry installandpoetry run python -m email_smtp_injector.openaev_email_smtp.Email (SMTP) - Craft emailcontract and send through Mailpit onlocalhost:1025.python -m unittest discover -s test,python -m pytest test,black --check .,isort --profile black --check ., andflake8 --ignore=E,W ..Related issues
Checklist
Further comments
The
do not mergelabel is intentional and remains in place as a maintainer-controlled merge-order gate for the email injector family. This PR has been approved only and was not merged.