Skip to content

feat(automation): add the Email REAction vertical (Run 4) - #116

Merged
RomeoCavazza merged 1 commit into
mainfrom
feat/run-4-email-reaction
Jul 29, 2026
Merged

feat(automation): add the Email REAction vertical (Run 4)#116
RomeoCavazza merged 1 commit into
mainfrom
feat/run-4-email-reaction

Conversation

@RomeoCavazza

Copy link
Copy Markdown
Member

Closes the Email REAction vertical, VIGIL-SVC-023.

A Team-scoped email connection stores five encrypted credentials (host, port, username, password, sender). When a rule fires, email_notify decrypts them, interpolates the subject and body templates and delivers through the existing durable run engine, recording success or failure on the connection like any other reaction.

What this fixes beyond the feature itself

Three defects made the service unusable or the pipeline red:

  • configure_service had no email arm. The catalogue advertised the connection and the client generated its form, but every save returned InvalidServiceConnection, so the connection could never be created.
  • testable: true was not backed by anything. The single /test route only accepted the http service, so the Test button always errored on an Email connection. It now runs a real SMTP probe (validate_smtp: authenticated session + NOOP, no message sent).
  • A failed delivery recorded the wrong error code. The failure path hardcoded email_transport_error, so a malformed recipient was reported to the Manager as an SMTP outage. It now records the actual code.

CI was also red before this branch: cargo clippy raised new_without_default and too_many_arguments (9/7) on the new code, and cargo deny rejected quoted_printable (0BSD), pulled by lettre for MIME encoding.

Design notes

  • EmailSender takes SmtpConfig and EmailMessage instead of nine positional arguments. Five consecutive &str were trivially swappable at a call site, which is what the clippy lint was pointing at.
  • validate_smtp mirrors Notifier::validate_endpoint, so configuration and execution share one boundary.
  • InvalidEmailSender separates a bad sender address from a bad recipient, at configuration time and at delivery time.
  • Creating a connection requires the full credential set; rotating one credential afterwards does not.

Coverage

A DummyEmailSender double covers template interpolation, the five decrypted credentials reaching the right fields, connection-service mismatch, recipient rejection, Manager-only configuration, incomplete credential sets, malformed port and sender, and that the probe never sends mail.

Local verification: cargo fmt, clippy -D warnings, cargo audit, cargo deny, 435 Rust tests, 86.80% source coverage (gate 70%), and the full web lint/format/typecheck/test/build suite all pass.

Not included

One live delivery to a real mailbox is still outstanding; VIGIL-SVC-023 stays PARTIAL until that proof exists.

Send an email through an SMTP relay when an automation rule fires, closing
VIGIL-SVC-023.

The Team-scoped `email` connection stores five encrypted credentials
(host, port, username, password, sender). `email_notify` decrypts them,
interpolates the subject and body templates, and delivers through the
existing durable run engine, so a failure is recorded on the connection
like any other reaction.

Implementation notes:

- `EmailSender` takes `SmtpConfig` and `EmailMessage` rather than nine
  positional arguments; five consecutive `&str` were trivially swappable
  at a call site.
- `validate_smtp` opens an authenticated session and issues NOOP without
  sending anything, so the catalogue's `testable: true` is now backed by
  a real probe instead of returning `InvalidServiceConnection`.
- `configure_service` gained its `email` arm. The catalogue advertised
  the connection and the client generated its form, but the endpoint
  rejected every save, leaving the whole service unreachable.
- A failed delivery records the actual error code; a malformed recipient
  was previously reported to the Manager as an SMTP outage.
- `InvalidEmailSender` distinguishes a bad sender address from a bad
  recipient, both at configuration time and at delivery time.
- `0BSD` joins the license allowlist: lettre pulls quoted_printable for
  MIME encoding, which cargo-deny rejected.

Covered by a `DummyEmailSender` double: template interpolation, the five
decrypted credentials, connection-service mismatch, recipient rejection,
Manager-only configuration, incomplete credential sets, and the probe
that must not send mail.
@RomeoCavazza
RomeoCavazza merged commit 71c5dd8 into main Jul 29, 2026
10 checks passed
@RomeoCavazza
RomeoCavazza deleted the feat/run-4-email-reaction branch July 29, 2026 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant