#!/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()