# ============================================================================== # Formeleon - Multi-tenant Form Generator # Author: Björn Runden # Module: services/mailer.py # Description: Service for handling email notifications and autoresponders. # ============================================================================== import smtplib import logging from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from typing import List, Dict, Any, Optional from flask import current_app from jinja2 import Template, TemplateError logger = logging.getLogger(__name__) class MailerService: """Service class for handling email dispatch.""" @classmethod def is_configured(cls) -> bool: """Check if SMTP settings are provided in configuration.""" server = current_app.config.get('SMTP_SERVER') # Wir betrachten den Service als "konfiguriert", wenn entweder ein Server da ist, # oder wir im Dev-Mode sind (um das Terminal-Logging zu erlauben) if current_app.config.get('DEV_MODE', False) and not server: return True port = current_app.config.get('SMTP_PORT') return bool(server and port) @classmethod def send_email(cls, to_addresses: List[str], subject: str, body_text: str, reply_to: Optional[str] = None) -> bool: """ Sends an email using the configured SMTP server. :param to_addresses: List of recipient email addresses :param subject: Email subject line :param body_text: Plain text email body :param reply_to: Optional Reply-To address :return: True if successful, False otherwise """ if not cls.is_configured(): logger.warning("MailerService: Attempted to send email, but SMTP is not configured.") return False if current_app.config.get('DEV_MODE', False) and not current_app.config.get('SMTP_SERVER'): logger.info(f"MailerService [DEV MODE]: Would send email to {to_addresses} | Subject: '{subject}'") return True smtp_server = current_app.config.get('SMTP_SERVER') smtp_port = int(current_app.config.get('SMTP_PORT', 587)) smtp_user = current_app.config.get('SMTP_USERNAME') smtp_pass = current_app.config.get('SMTP_PASSWORD') use_tls = str(current_app.config.get('SMTP_USE_TLS', 'true')).lower() == 'true' from_address = current_app.config.get('EMAIL_FROM', 'formeleon@localhost') msg = MIMEMultipart() msg['From'] = from_address msg['To'] = ", ".join(to_addresses) msg['Subject'] = subject if reply_to: msg['Reply-To'] = reply_to msg.attach(MIMEText(body_text, 'plain', 'utf-8')) try: with smtplib.SMTP(smtp_server, smtp_port) as server: if use_tls: server.starttls() if smtp_user and smtp_pass: server.login(smtp_user, smtp_pass) server.send_message(msg) logger.info(f"Successfully sent email to {to_addresses}") return True except Exception as e: logger.error(f"Failed to send email to {to_addresses}: {str(e)}", exc_info=True) return False @classmethod def render_template(cls, template_string: str, payload: Dict[str, Any]) -> str: """ Renders a Jinja2 template string using the submission payload. :param template_string: The template string (e.g., "Hello {{ name }}") :param payload: Dictionary containing the form submission data :return: Rendered string """ try: template = Template(template_string) return template.render(**payload) except TemplateError as e: logger.error(f"Template rendering failed: {str(e)}") # Fallback: Return original string if rendering fails return template_string @classmethod def send_admin_notification(cls, form_name: str, to_addresses: List[str], payload: Dict[str, Any]) -> bool: """ Sends an alert to the tenant admins about a new submission. """ subject = f"New Submission: {form_name}" # Build a simple readable key-value body body_lines = [f"A new submission was received for the form '{form_name}'.\n", "-" * 40] for key, value in payload.items(): body_lines.append(f"{key}: {value}") body_lines.append("-" * 40) body_text = "\n".join(body_lines) return cls.send_email(to_addresses, subject, body_text) @classmethod def send_autoresponder(cls, to_address: str, subject_template: str, body_template: str, payload: Dict[str, Any]) -> bool: """ Sends a templated autoresponder to the person who submitted the form. """ subject = cls.render_template(subject_template, payload) body = cls.render_template(body_template, payload) return cls.send_email([to_address], subject, body)