Files
public-tools/smtp_send.py

134 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
smtp_send.py
Send a plain-text alert message via direct SMTP to the PSI mail infrastructure.
This script deliberately does NOT use local sendmail.
Design goals:
- Explicit SMTP connection to smtp.psi.ch
- No SMTP authentication (assumes trusted internal network)
- Fixed default sender address (can be overridden)
- Predictable exit codes and errors for use from shell scripts
"""
from __future__ import annotations
import argparse
import sys
import smtplib
from email.message import EmailMessage
from email.utils import formatdate
from typing import List
DEFAULT_SMTP_SERVER = "smtp.psi.ch"
DEFAULT_SMTP_PORT = 25
DEFAULT_TIMEOUT_S = 10.0
DEFAULT_FROM_ADDR = "cSAXS@psi.ch"
DEFAULT_SUBJECT = "EPICS alert"
def build_message(
*, from_addr: str, to_addrs: List[str], subject: str, body: str
) -> EmailMessage:
msg = EmailMessage()
msg["From"] = from_addr
msg["To"] = ", ".join(to_addrs)
msg["Subject"] = subject
msg["Date"] = formatdate(localtime=True)
msg.set_content(body)
return msg
def send_email_via_smtp(
*,
to_addrs: List[str],
body: str,
subject: str = DEFAULT_SUBJECT,
from_addr: str = DEFAULT_FROM_ADDR,
smtp_server: str = DEFAULT_SMTP_SERVER,
smtp_port: int = DEFAULT_SMTP_PORT,
timeout_s: float = DEFAULT_TIMEOUT_S,
) -> None:
msg = build_message(
from_addr=from_addr, to_addrs=to_addrs, subject=subject, body=body
)
with smtplib.SMTP(smtp_server, smtp_port, timeout=timeout_s) as smtp:
# Explicit envelope sender/recipients (avoid relying on headers for SMTP routing)
smtp.send_message(msg, from_addr=from_addr, to_addrs=to_addrs)
def parse_args(argv: List[str]) -> argparse.Namespace:
p = argparse.ArgumentParser(
description="Send a plain-text alert email via direct SMTP (no local sendmail)."
)
p.add_argument(
"--to",
dest="to_addrs",
action="append",
required=True,
help="Recipient address. Repeat --to for multiple recipients.",
)
p.add_argument(
"message",
nargs="?",
default="-",
help="Message body. Use '-' (default) to read the body from stdin.",
)
p.add_argument("--subject", default=DEFAULT_SUBJECT, help="Email subject.")
p.add_argument(
"--from-addr", default=DEFAULT_FROM_ADDR, help="Envelope/header From address."
)
p.add_argument(
"--server", default=DEFAULT_SMTP_SERVER, help="SMTP server hostname."
)
p.add_argument(
"--port", type=int, default=DEFAULT_SMTP_PORT, help="SMTP server port."
)
p.add_argument(
"--timeout",
type=float,
default=DEFAULT_TIMEOUT_S,
help="SMTP timeout (seconds).",
)
return p.parse_args(argv)
def main(argv: List[str]) -> int:
args = parse_args(argv)
if args.message == "-":
body = sys.stdin.read()
else:
body = args.message
# Make the "empty message" case explicit: its almost always a bug in alerting.
if not body.strip():
print("ERROR: message body is empty", file=sys.stderr)
return 2
try:
send_email_via_smtp(
to_addrs=args.to_addrs,
body=body,
subject=args.subject,
from_addr=args.from_addr,
smtp_server=args.server,
smtp_port=args.port,
timeout_s=args.timeout,
)
except (OSError, smtplib.SMTPException) as e:
print(
f"ERROR: failed to send email via {args.server}:{args.port}: {e}",
file=sys.stderr,
)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))