Files
zwitschi e192086833
All checks were successful
CI / test (3.11) (push) Successful in 1m36s
CI / build-image (push) Successful in 1m27s
feat: Add email settings management and templates functionality
- Implemented email settings configuration in the admin panel, allowing for SMTP settings and notification preferences.
- Created a new template for email settings with fields for SMTP host, port, username, password, sender address, and recipients.
- Added JavaScript functionality to handle loading, saving, and validating email settings.
- Introduced email templates management, enabling the listing, editing, and saving of email templates.
- Updated navigation to include links to email settings and templates.
- Added tests for email settings and templates to ensure proper functionality and validation.
2025-11-15 11:12:23 +01:00

122 lines
4.0 KiB
Python

"""Business logic for contact submissions."""
from __future__ import annotations
import logging
import smtplib
from dataclasses import dataclass
from datetime import datetime, timezone
from email.message import EmailMessage
from typing import Any, Dict, Tuple, cast
from ..database import save_contact
from ..metrics import record_submission
from ..utils import is_valid_email
from .email_settings import load_effective_smtp_settings
@dataclass
class ContactSubmission:
name: str
email: str
company: str | None
message: str
timeline: str | None
created_at: str = datetime.now(timezone.utc).isoformat()
def validate_submission(raw: Dict[str, Any]) -> Tuple[ContactSubmission | None, Dict[str, str]]:
"""Validate the incoming payload and return a submission object."""
name = (raw.get("name") or "").strip()
email = (raw.get("email") or "").strip()
message = (raw.get("message") or "").strip()
consent = raw.get("consent")
company = (raw.get("company") or "").strip()
errors: Dict[str, str] = {}
if not name:
errors["name"] = "Name is required."
elif len(name) > 200:
errors["name"] = "Name is too long (max 200 chars)."
if not is_valid_email(email):
errors["email"] = "Valid email is required."
if not message:
errors["message"] = "Message is required."
elif len(message) > 5000:
errors["message"] = "Message is too long (max 5000 chars)."
if not consent:
errors["consent"] = "Consent is required."
if company and len(company) > 200:
errors["company"] = "Organisation name is too long (max 200 chars)."
if errors:
return None, errors
submission = ContactSubmission(
name=name,
email=email,
company=company or None,
message=message,
timeline=(raw.get("timeline") or "").strip() or None,
)
return submission, {}
def persist_submission(submission: ContactSubmission) -> int:
"""Persist the submission and update metrics."""
record_id = save_contact(submission)
record_submission()
return record_id
def send_notification(submission: ContactSubmission) -> bool:
"""Send an email notification for the submission if SMTP is configured."""
smtp_config = load_effective_smtp_settings()
if not smtp_config.get("notify_contact_form"):
logging.info("Contact notifications disabled; skipping email dispatch")
return False
if not smtp_config.get("host") or not smtp_config.get("recipients"):
logging.info("SMTP not configured; skipping email notification")
return False
sender = smtp_config.get("sender") or "no-reply@example.com"
recipients = smtp_config.get("recipients", [])
host = cast(str, smtp_config.get("host"))
port = int(smtp_config.get("port") or 0)
username = smtp_config.get("username") or None
password = smtp_config.get("password") or ""
msg = EmailMessage()
msg["Subject"] = f"Neue Kontaktanfrage von {submission.name}"
msg["From"] = sender
msg["To"] = ", ".join(recipients)
msg.set_content(
"\n".join(
[
f"Name: {submission.name}",
f"E-Mail: {submission.email}",
f"Organisation: {submission.company or ''}",
f"Zeithorizont: {submission.timeline or ''}",
"",
"Nachricht:",
submission.message,
"",
f"Eingang: {submission.created_at}",
]
)
)
try:
with smtplib.SMTP(host, port, timeout=15) as server:
if smtp_config.get("use_tls"):
server.starttls()
if username:
server.login(username, password)
server.send_message(msg)
logging.info("Notification email dispatched to %s", recipients)
return True
except Exception as exc: # pragma: no cover - SMTP failures are logged only
logging.error("Failed to send notification email: %s", exc)
return False