Aggiunge invio prenotazioni con Mailjet
This commit is contained in:
@@ -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=
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]}")
|
||||
|
||||
Reference in New Issue
Block a user