summaryrefslogtreecommitdiff
path: root/Biz
diff options
context:
space:
mode:
Diffstat (limited to 'Biz')
-rwxr-xr-xBiz/EmailAgent.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/Biz/EmailAgent.py b/Biz/EmailAgent.py
new file mode 100755
index 0000000..6ac4c95
--- /dev/null
+++ b/Biz/EmailAgent.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env run.sh
+"""
+Email sending utility that can be used as a script or imported as a library.
+
+Password is provided through systemd's LoadCredential feature. This is intended
+to be used by automated agents in a systemd timer.
+"""
+
+import argparse
+import email.message
+import email.utils
+import errno
+import os
+import pathlib
+import smtplib
+import sys
+
+
+# ruff: noqa: PLR0917, PLR0913
+def send_email(
+ to_addrs: list[str],
+ from_addr: str,
+ smtp_server: str,
+ password: str,
+ subject: str,
+ body_text: pathlib.Path,
+ body_html: pathlib.Path | None = None,
+ port: int = 587,
+) -> dict[str, tuple[int, bytes]]:
+ """
+ Send an email using the provided parameters.
+
+ Args:
+ to_addr: Recipient email addresses
+ from_addr: Sender email address
+ smtp_server: SMTP server hostname
+ password: Password for authentication
+ subject: Email subject
+ body_text: File with email body text
+ body_html: File with email body html
+ port: SMTP server port (default: 587)
+
+ """
+ msg = email.message.EmailMessage()
+ msg["Subject"] = subject
+ msg["From"] = from_addr
+ msg["To"] = ", ".join(to_addrs)
+ msg["Message-ID"] = email.utils.make_msgid(
+ idstring=__name__,
+ domain=smtp_server,
+ )
+ msg["Date"] = email.utils.formatdate(localtime=True)
+ with body_text.open(encoding="utf-8") as txt:
+ msg.set_content(txt.read())
+ if body_html:
+ with body_html.open(encoding="utf-*") as html:
+ msg.add_alternative(html.read(), subtype="html")
+ with smtplib.SMTP(smtp_server, port) as server:
+ server.starttls()
+ server.login(from_addr, password)
+ return server.send_message(
+ msg,
+ from_addr=from_addr,
+ to_addrs=to_addrs,
+ )
+
+
+def main() -> None:
+ """Parse command line arguments and send email.
+
+ Raises:
+ FileNotFoundError: if --password-file does not exist
+ """
+ if "test" in sys.argv:
+ sys.exit(0)
+ parser = argparse.ArgumentParser(
+ description="Send an email",
+ )
+ parser.add_argument(
+ "--to",
+ required=True,
+ help="Recipient email addresses, can be specified multiple times",
+ nargs="+",
+ action="extend",
+ )
+ parser.add_argument(
+ "--from",
+ dest="from_addr",
+ required=True,
+ help="Sender email address",
+ )
+ parser.add_argument(
+ "--smtp-server",
+ required=True,
+ help="SMTP server hostname",
+ )
+ parser.add_argument("--subject", required=True, help="Email subject")
+ parser.add_argument(
+ "--body-text",
+ required=True,
+ help="File with email body text",
+ )
+ parser.add_argument(
+ "--body-html",
+ help="File with email body html",
+ default=None,
+ )
+ parser.add_argument(
+ "--port",
+ type=int,
+ default=587,
+ help="SMTP server port (default: 587)",
+ )
+ parser.add_argument(
+ "--password-file",
+ default="smtp-password",
+ help="Where to find the password file",
+ )
+
+ args = parser.parse_args()
+
+ credential_path = pathlib.Path(args.password_file)
+ if not credential_path.exists():
+ raise FileNotFoundError(
+ errno.ENOENT,
+ os.strerror(errno.ENOENT),
+ credential_path,
+ )
+ sys.exit(1)
+
+ with pathlib.Path.open(credential_path, encoding="utf-8") as f:
+ password = f.read().strip()
+
+ results = send_email(
+ to_addrs=args.to,
+ from_addr=args.from_addr,
+ smtp_server=args.smtp_server,
+ subject=args.subject,
+ body_text=pathlib.Path(args.body_text),
+ body_html=pathlib.Path(args.body_html) if args.body_html else None,
+ password=password,
+ port=args.port,
+ )
+ if len(results) > 0:
+ sys.stdout.write(str(results))
+ sys.stdout.flush()
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()