Aggiunge invio prenotazioni con Mailjet

This commit is contained in:
2026-09-04 15:46:18 +02:00
parent 8d83f0c619
commit ba4a73d5ca
3 changed files with 158 additions and 20 deletions

View File

@@ -3,11 +3,25 @@ APP_ENV=development
APP_HOST=127.0.0.1
APP_PORT=8000
DATABASE_URL=sqlite:///C:/devel/clinica_veterinaria_formiginese/data/clinica.db
MAIL_PROVIDER=mailjet
MAILJET_API_KEY=
MAILJET_API_SECRET=
MAILJET_API_URL=https://api.mailjet.com/v3.1/send
MAILJET_SANDBOX_MODE=true
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_USE_TLS=true
BOOKING_EMAIL_FROM=
BOOKING_EMAIL_FROM_NAME=Clinica Veterinaria Formiginese
BOOKING_EMAIL_TO=
BOOKING_EMAIL_SUBJECT_PREFIX=[Clinica Veterinaria Formiginese]
BOOKING_FORM_ENABLED=false
BOOKING_AUDIT_LOG_PATH=
BOOKING_RATE_LIMIT_WINDOW_SECONDS=900
BOOKING_RATE_LIMIT_MAX_REQUESTS=5
BOOKING_CAPTCHA_TTL_SECONDS=600
BOOKING_CAPTCHA_SECRET=

View File

@@ -27,11 +27,22 @@ class Settings:
self.smtp_password = os.getenv("SMTP_PASSWORD", "")
self.smtp_use_tls = os.getenv("SMTP_USE_TLS", "true").lower() in {"1", "true", "yes", "on"}
self.booking_email_from = os.getenv("BOOKING_EMAIL_FROM", self.smtp_username)
self.booking_email_from_name = os.getenv("BOOKING_EMAIL_FROM_NAME", "Clinica Veterinaria Formiginese")
self.booking_email_to = os.getenv("BOOKING_EMAIL_TO", "")
self.booking_email_subject_prefix = os.getenv(
"BOOKING_EMAIL_SUBJECT_PREFIX",
"[Clinica Veterinaria Formiginese]",
)
self.mail_provider = os.getenv("MAIL_PROVIDER", "mailjet").lower()
self.mailjet_api_key = os.getenv("MAILJET_API_KEY", "")
self.mailjet_api_secret = os.getenv("MAILJET_API_SECRET", "")
self.mailjet_api_url = os.getenv("MAILJET_API_URL", "https://api.mailjet.com/v3.1/send")
self.mailjet_sandbox_mode = os.getenv("MAILJET_SANDBOX_MODE", "false").lower() in {
"1",
"true",
"yes",
"on",
}
self.booking_form_enabled = os.getenv("BOOKING_FORM_ENABLED", "false").lower() in {
"1",
"true",
@@ -59,6 +70,17 @@ class Settings:
@property
def booking_mail_enabled(self) -> bool:
if self.mail_provider == "mailjet":
return all(
[
self.mailjet_api_key,
self.mailjet_api_secret,
self.booking_email_from,
self.booking_email_to,
]
)
if self.mail_provider == "smtp":
return all(
[
self.smtp_host,
@@ -70,5 +92,7 @@ class Settings:
]
)
return False
settings = Settings()

View File

@@ -1,8 +1,13 @@
from __future__ import annotations
import base64
from datetime import datetime
from email.message import EmailMessage
from html import escape
import json
import smtplib
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
from app.config import settings
from app.schemas.booking import BookingRequestCreate
@@ -12,28 +17,42 @@ class MailConfigurationError(RuntimeError):
pass
class MailDeliveryError(RuntimeError):
pass
def _build_booking_subject(payload: BookingRequestCreate) -> str:
return f"{settings.booking_email_subject_prefix} Nuova richiesta visita - {payload.name}"
def _build_booking_html(payload: BookingRequestCreate) -> str:
submitted_at = datetime.now().strftime("%d/%m/%Y %H:%M")
name = escape(payload.name)
email = escape(payload.email)
phone = escape(payload.phone)
pet_name = escape(payload.pet_name or "-")
pet_type = escape(payload.pet_type)
doctor = escape(payload.doctor)
service = escape(payload.service)
date = escape(payload.date)
time = escape(payload.time or "Qualsiasi orario")
notes = escape(payload.notes or "-")
return f"""
<html>
<body style="font-family: Arial, sans-serif; color: #1f2937; line-height: 1.6;">
<h2 style="color: #1B4F72;">Nuova richiesta di prenotazione visita</h2>
<p>È stata inviata una nuova richiesta di prenotazione non vincolante dal sito web.</p>
<table cellpadding="8" cellspacing="0" border="0" style="border-collapse: collapse;">
<tr><td><strong>Nome e cognome</strong></td><td>{payload.name}</td></tr>
<tr><td><strong>Email</strong></td><td>{payload.email}</td></tr>
<tr><td><strong>Telefono</strong></td><td>{payload.phone}</td></tr>
<tr><td><strong>Nome animale</strong></td><td>{payload.pet_name or "-"}</td></tr>
<tr><td><strong>Tipo animale</strong></td><td>{payload.pet_type}</td></tr>
<tr><td><strong>Medico richiesto</strong></td><td>{payload.doctor}</td></tr>
<tr><td><strong>Tipo di visita</strong></td><td>{payload.service}</td></tr>
<tr><td><strong>Periodo preferito</strong></td><td>{payload.date}</td></tr>
<tr><td><strong>Fascia oraria preferita</strong></td><td>{payload.time or "Qualsiasi orario"}</td></tr>
<tr><td><strong>Note</strong></td><td>{payload.notes or "-"}</td></tr>
<tr><td><strong>Nome e cognome</strong></td><td>{name}</td></tr>
<tr><td><strong>Email</strong></td><td>{email}</td></tr>
<tr><td><strong>Telefono</strong></td><td>{phone}</td></tr>
<tr><td><strong>Nome animale</strong></td><td>{pet_name}</td></tr>
<tr><td><strong>Tipo animale</strong></td><td>{pet_type}</td></tr>
<tr><td><strong>Medico richiesto</strong></td><td>{doctor}</td></tr>
<tr><td><strong>Tipo di visita</strong></td><td>{service}</td></tr>
<tr><td><strong>Periodo preferito</strong></td><td>{date}</td></tr>
<tr><td><strong>Fascia oraria preferita</strong></td><td>{time}</td></tr>
<tr><td><strong>Note</strong></td><td>{notes}</td></tr>
<tr><td><strong>Inviata il</strong></td><td>{submitted_at}</td></tr>
</table>
<p style="margin-top: 20px;">
@@ -65,6 +84,21 @@ def _build_booking_text(payload: BookingRequestCreate) -> str:
def send_booking_request_email(payload: BookingRequestCreate) -> None:
if not settings.booking_mail_enabled:
raise MailConfigurationError(f"Configurazione email incompleta per provider {settings.mail_provider}.")
if settings.mail_provider == "mailjet":
_send_booking_request_email_mailjet(payload)
return
if settings.mail_provider == "smtp":
_send_booking_request_email_smtp(payload)
return
raise MailConfigurationError(f"Provider email non supportato: {settings.mail_provider}")
def _send_booking_request_email_smtp(payload: BookingRequestCreate) -> None:
if not settings.booking_mail_enabled:
raise MailConfigurationError("Configurazione SMTP incompleta.")
@@ -81,3 +115,69 @@ def send_booking_request_email(payload: BookingRequestCreate) -> None:
server.starttls()
server.login(settings.smtp_username, settings.smtp_password)
server.send_message(message)
def _send_booking_request_email_mailjet(payload: BookingRequestCreate) -> None:
mailjet_payload = {
"Messages": [
{
"From": {
"Email": settings.booking_email_from,
"Name": settings.booking_email_from_name,
},
"To": [
{
"Email": settings.booking_email_to,
"Name": "Clinica Veterinaria Formiginese",
}
],
"ReplyTo": {
"Email": payload.email,
"Name": payload.name,
},
"Subject": _build_booking_subject(payload),
"TextPart": _build_booking_text(payload),
"HTMLPart": _build_booking_html(payload),
}
]
}
if settings.mailjet_sandbox_mode:
mailjet_payload["SandboxMode"] = True
auth_token = base64.b64encode(
f"{settings.mailjet_api_key}:{settings.mailjet_api_secret}".encode("utf-8")
).decode("ascii")
request = Request(
settings.mailjet_api_url,
data=json.dumps(mailjet_payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": f"Basic {auth_token}",
},
method="POST",
)
try:
with urlopen(request, timeout=30) as response:
response_body = response.read().decode("utf-8")
response_status = response.status
except HTTPError as exc:
error_body = exc.read().decode("utf-8", errors="replace")
raise MailDeliveryError(f"Mailjet API HTTP {exc.code}: {error_body[:500]}") from exc
except URLError as exc:
raise MailDeliveryError(f"Mailjet API connection error: {exc.reason}") from exc
if response_status < 200 or response_status >= 300:
raise MailDeliveryError(f"Mailjet API HTTP {response_status}: {response_body[:500]}")
try:
response_payload = json.loads(response_body)
except json.JSONDecodeError as exc:
raise MailDeliveryError(f"Mailjet API response non JSON: {response_body[:500]}") from exc
messages = response_payload.get("Messages", [])
message_status = messages[0].get("Status") if messages else None
if message_status != "success":
raise MailDeliveryError(f"Mailjet API delivery status {message_status}: {response_body[:500]}")