SMTP Check Executor¶
NyxMon’s SMTP executor sends a real message through an authenticated SMTP relay and marks the check as successful once the server accepts the message after DATA.
Configuration¶
Store the executor settings in check.data:
{
"host": "smtp.home.wersdoerfer.de",
"port": 587,
"tls": "starttls", # "none" | "starttls" | "implicit"
"username": "monitor@xn--wersdrfer-47a.de",
"password_secret": "nyxmon_local_monitor_password", # or use "password"
"from_addr": "monitor@xn--wersdrfer-47a.de",
"to_addr": "wersdoerfer.mailmon@gmail.com",
"subject_prefix": "[nyxmon-outbound]",
"timeout": 30,
"retries": 2,
"retry_delay": 5
}
Validation rules:
host,from_addr,to_addr, andsubject_prefixare required.tlsmust be one ofnone,starttls, orimplicit.If
usernameis set, eitherpasswordorpassword_secretmust be provided.retriesandretry_delaymust be zero or positive.
Behaviour¶
Performs STARTTLS or implicit TLS when requested.
Authenticates before sending when credentials are provided.
Builds subjects as
<prefix> <UTC timestamp> <6-char token>and returns the token for IMAP correlation.Retries on 4xx responses (greylisting/temporary failures) with configurable backoff.
Fails fast on 5xx errors, auth failures, connection errors, and timeouts.
Drives the IMAP inbound check: keep
subject_prefixin sync with the IMAPsearch_subject, and send at least once per IMAP interval so the IMAP check can find a fresh message.
Integration Test Plan (Mail Monitoring PRD)¶
Prerequisites
Postfix running on macmini with submission (
587) and STARTTLS enabled.Monitor mailbox credentials (local
monitor@+ Gmail app password) available in secrets.Postgrey enabled on the edge to exercise greylisting.
Happy-path send
Create a check with the configuration above and set
retries=2,retry_delay=5.Execute once via a quick harness:
PYTHONPATH=src uv run python - <<'PY' import anyio from nyxmon.domain import Check, CheckType from nyxmon.adapters.runner.executors.smtp_executor import SmtpCheckExecutor check = Check( check_id=1, service_id=1, check_type=CheckType.SMTP, url="smtp.home.wersdoerfer.de", data={ "host": "smtp.home.wersdoerfer.de", "port": 587, "tls": "starttls", "username": "monitor@xn--wersdrfer-47a.de", "password": "<app-password>", "from_addr": "monitor@xn--wersdrfer-47a.de", "to_addr": "wersdoerfer.mailmon@gmail.com", "subject_prefix": "[nyxmon-outbound]", }, ) async def main(): executor = SmtpCheckExecutor() result = await executor.execute(check) print(result.status, result.data) await executor.aclose() anyio.run(main) PY
Verify
result.status == "ok"and Postfix logs show acceptance onDATA.
Greylisting path
Enable Postgrey, clear its cache, and repeat. Expect one retry with
error_type="temporary_failure"on the first attempt and success on the second/third.Confirm the final result reports
attempts> 1 and includes the correlation token for the IMAP check.
Failure modes
Provide an invalid password to confirm
error_type="auth_error"and no retries.Stop Postfix to confirm a single
connection_error.
Record findings in bd comments when running these steps against staging/live mail.