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

160 lines
5.1 KiB
Python

"""Business logic for newsletter subscriptions."""
from __future__ import annotations
from datetime import datetime, timezone
from typing import cast
from ..database import save_subscriber, delete_subscriber, update_subscriber
from ..utils import is_valid_email
from .email_settings import load_effective_smtp_settings
from .email_templates import get_template_content
def validate_email(email: str) -> bool:
"""Return True when the provided email passes a basic sanity check."""
return is_valid_email(email)
def subscribe(email: str) -> bool:
"""Persist the subscription and return False when it already exists."""
created_at = datetime.now(timezone.utc).isoformat()
return save_subscriber(email, created_at=created_at)
def unsubscribe(email: str) -> bool:
"""Remove the subscription and return True if it existed."""
return delete_subscriber(email)
def update_email(old_email: str, new_email: str) -> bool:
"""Update the email for a subscription. Return True if updated."""
return update_subscriber(old_email, new_email)
def send_subscription_confirmation(email: str) -> bool:
"""Send a confirmation email to the subscriber."""
import logging
smtp_config = load_effective_smtp_settings()
if not smtp_config.get("notify_newsletter_signups"):
logging.info(
"Newsletter signup notifications disabled; skipping email")
return False
host = smtp_config.get("host")
port = int(smtp_config.get("port") or 0)
username = smtp_config.get("username")
password = smtp_config.get("password") or ""
if not host:
logging.info("SMTP not configured; skipping confirmation email")
return False
# Get template content (persisted or default)
template = get_template_content("newsletter_confirmation")
try:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
sender = smtp_config.get("sender") or "noreply@example.com"
# Create message
msg = MIMEMultipart('alternative')
msg['Subject'] = "Newsletter Subscription Confirmation"
msg['From'] = sender
msg['To'] = email
# Add HTML content
html_part = MIMEText(template, 'html')
msg.attach(html_part)
# Send email
with smtplib.SMTP(cast(str, 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("Confirmation email sent to %s", email)
return True
except Exception as exc:
logging.exception(
"Failed to send confirmation email to %s: %s", email, exc)
return False
def send_newsletter_to_subscribers(subject: str, content: str, emails: list[str], sender_name: str | None = None) -> int:
"""Send newsletter to list of email addresses. Returns count of successful sends."""
import logging
smtp_config = load_effective_smtp_settings()
host = smtp_config.get("host")
port = int(smtp_config.get("port") or 0)
username = smtp_config.get("username")
password = smtp_config.get("password")
if not host:
logging.error("SMTP not configured, cannot send newsletter")
return 0
try:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Create message
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = smtp_config.get("sender") or "noreply@example.com"
# Format content
formatted_content = content.replace('\n', '<br>')
html_content = f"""
<html>
<body>
{formatted_content}
</body>
</html>
"""
# Add HTML content
html_part = MIMEText(html_content, 'html')
msg.attach(html_part)
# Send to each recipient individually for better deliverability
success_count = 0
with smtplib.SMTP(cast(str, host), port) as server:
if smtp_config.get("use_tls"):
server.starttls()
if username and password is not None:
server.login(username, password)
for email in emails:
try:
# Create a fresh copy for each recipient
recipient_msg = MIMEMultipart('alternative')
recipient_msg['Subject'] = subject
recipient_msg['From'] = msg['From']
recipient_msg['To'] = email
# Add HTML content
recipient_msg.attach(MIMEText(html_content, 'html'))
server.sendmail(msg['From'], email,
recipient_msg.as_string())
success_count += 1
except Exception as exc:
logging.exception(
"Failed to send newsletter to %s: %s", email, exc)
return success_count
except Exception as exc:
logging.exception("Failed to send newsletter: %s", exc)
return 0