from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication import smtplib import time import logging import Web.modules.database.settings as cfg logger = logging.getLogger(__name__) def _build_smtp_client(): smtp = smtplib.SMTP( "mail.invario-software.de", 587, timeout=cfg.EMAIL_TIMEOUT_SECONDS, ) smtp.ehlo() if cfg.EMAIL_USE_TLS: smtp.starttls() smtp.ehlo() if cfg.EMAIL_USERNAME: smtp.login(cfg.EMAIL_USERNAME, cfg.EMAIL_PASSWORD or "") return smtp def _normalize_recipients(email: list | str) -> list[str]: if isinstance(email, str): email = email.replace(';', ',').split(',') return [str(recipient).strip() for recipient in (email or []) if str(recipient).strip()] def _send_message(email: list | str, subject: str, note: str, sender: str, attachment=None) -> bool: """Send a plain/HTML message, optionally with one PDF attachment.""" if not cfg.MODULES.is_enabled("mail"): logger.info("Email delivery skipped because the mail module is disabled") return False recipients = _normalize_recipients(email) if not recipients: logger.warning("Email delivery skipped because no recipients were provided") return False body_message = note HTML_SIGNATURE = f"""

Mit freundlichen Grüßen

Automatisierter Email Verteiler für die Schule: {cfg.get_school_info().get("name")}


Invario UG
Am Sportplatz 10
83052 Bruckmühl

""" text_content = f"{body_message}\n\nMit freundlichen Grüßen\n{sender}\n" html_content = f"""

{body_message}


{HTML_SIGNATURE} """ mails_per_second = 10 interval = 1.0 / mails_per_second smtp = None try: smtp = _build_smtp_client() for i, recipient in enumerate(recipients): start_time = time.time() msg = MIMEMultipart("mixed" if attachment else "alternative") msg["Subject"] = str(subject) from_address = cfg.EMAIL_FROM_ADDRESS or cfg.EMAIL_USERNAME or "no-reply@invario-software.de" msg["From"] = f"{sender} <{from_address}>" msg["To"] = str(recipient) body = MIMEMultipart("alternative") if attachment else msg body.attach(MIMEText(text_content, "plain")) body.attach(MIMEText(html_content, "html")) if attachment: msg.attach(body) attachment_payload, filename = attachment pdf_part = MIMEApplication(attachment_payload, _subtype="pdf") pdf_part.add_header("Content-Disposition", "attachment", filename=filename) msg.attach(pdf_part) smtp.sendmail( from_addr=from_address, to_addrs=[recipient], msg=msg.as_string() ) elapsed_time = time.time() - start_time sleep_time = interval - elapsed_time if sleep_time > 0 and i < len(recipients) - 1: time.sleep(sleep_time) return True except Exception as e: logger.exception("Email delivery failed: %s", e) return False finally: if smtp: try: smtp.quit() except Exception: pass def send(email: list | str, subject: str, note: str, sender: str) -> bool: """Send an HTML/plain email without an attachment.""" return _send_message(email, subject, note, sender) def send_pdf(email: list | str, subject: str, note: str, sender: str, pdf_bytes: bytes, filename: str) -> bool: """Send an email with a PDF attachment.""" return _send_message(email, subject, note, sender, attachment=(pdf_bytes, filename))