Terminal / api /email.py
Pulka
feat(telegram): bot UX 100% — WebApp button, /logs, /ping, inline query, _BACK_KB
4cbd6c4 verified
Raw
History Blame
6.25 kB
"""
email.py — Invio email via Resend API (https://resend.com).
Endpoint:
POST /api/email/send — Invia email transazionale/markdown
Configurazione:
RESEND_API_KEY env var — richiesto (chiave Resend, formato re_...)
Default from: noreply@<dominio configurato in Resend>
Comportamento:
- body può essere testo plain o HTML
- Se html=true, body viene inviato come HTML (altrimenti text/plain)
- Supporta cc, bcc, reply_to, allegati (future extension)
- Rate limit Resend free tier: 100 email/day, 1 email/sec
Error handling:
- Resend 422: validazione campi (to/from non validi)
- Resend 429: rate limit
- Mancanza RESEND_API_KEY → istruzione chiara per configurazione
"""
from __future__ import annotations
import os, logging, httpx
from fastapi import APIRouter, Request
from pydantic import BaseModel, EmailStr, field_validator
from typing import Optional, List
router = APIRouter(prefix="/api/email", tags=["email"])
_logger = logging.getLogger("email_api")
# ── Modelli ─────────────────────────────────────────────────────────────────
class SendEmailRequest(BaseModel):
to: str # destinatario (email o "Nome <email>")
subject: str # oggetto
body: str # corpo (testo plain o HTML)
html: bool = False # True → invia come text/html
from_name: str = "Agente AI"
from_email: str = "" # auto: noreply@dominio o default Resend
cc: List[str] = []
bcc: List[str] = []
reply_to: str = ""
@field_validator("subject", "body")
@classmethod
def not_empty(cls, v: str) -> str: # noqa: N805
if not v.strip():
raise ValueError("Il campo non può essere vuoto")
return v.strip()
class SendEmailResponse(BaseModel):
ok: bool
id: Optional[str] = None
message: str
provider: str = "resend"
# ── Endpoint ─────────────────────────────────────────────────────────────────
@router.post("/send", response_model=SendEmailResponse)
async def send_email(req: SendEmailRequest, request: Request) -> SendEmailResponse:
"""
Invia email via Resend API.
Richiede RESEND_API_KEY nell'ambiente del backend HF Space.
"""
_internal_token = os.getenv('INTERNAL_TOKEN', '')
if _internal_token and request.headers.get('X-Internal-Token') != _internal_token:
raise HTTPException(401, 'Unauthorized')
api_key = os.getenv("RESEND_API_KEY", "")
if not api_key:
_logger.warning("RESEND_API_KEY non configurata")
return SendEmailResponse(
ok=False,
message=(
"❌ RESEND_API_KEY non configurata. "
"Per abilitare send_email: "
"1. Registrati su https://resend.com (gratuito fino a 3.000 email/mese) "
"2. Crea un API key in Dashboard → API Keys "
"3. Aggiungi RESEND_API_KEY nei Secrets dell'HF Space "
"4. (opzionale) Verifica il tuo dominio per il campo 'from'"
),
)
# Costruisci mittente
from_email = req.from_email or os.getenv("RESEND_FROM_EMAIL", "noreply@resend.dev")
from_field = f"{req.from_name} <{from_email}>" if req.from_name else from_email
# Payload Resend
payload: dict = {
"from": from_field,
"to": [req.to] if isinstance(req.to, str) else req.to,
"subject": req.subject,
}
if req.html:
payload["html"] = req.body
else:
payload["text"] = req.body
if req.cc:
payload["cc"] = req.cc
if req.bcc:
payload["bcc"] = req.bcc
if req.reply_to:
payload["reply_to"] = req.reply_to
try:
async with httpx.AsyncClient(timeout=20.0) as client:
resp = await client.post(
"https://api.resend.com/emails",
json=payload,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
)
if resp.status_code in (200, 201):
data = resp.json()
email_id = data.get("id", "")
_logger.info("Email inviata OK → id=%s to=%s", email_id, req.to)
return SendEmailResponse(
ok=True,
id=email_id,
message=f"✅ Email inviata con successo a {req.to} (id: {email_id})",
)
# Gestione errori specifici Resend
err_body: dict = {}
try:
err_body = resp.json()
except Exception as _exc:
_logger.debug("[email] silenced %s", type(_exc).__name__) # noqa: BLE001
err_msg = err_body.get("message") or err_body.get("error") or resp.text[:200]
if resp.status_code == 422:
return SendEmailResponse(
ok=False,
message=f"❌ Email non valida: {err_msg}. Verifica indirizzo 'to' e 'from'.",
)
if resp.status_code == 429:
return SendEmailResponse(
ok=False,
message="❌ Rate limit Resend raggiunto (100 email/giorno sul piano free). Riprova tra qualche ora.",
)
if resp.status_code == 401:
return SendEmailResponse(
ok=False,
message="❌ RESEND_API_KEY non valida. Verifica il valore nei Secrets dell'HF Space.",
)
return SendEmailResponse(
ok=False,
message=f"❌ Resend errore {resp.status_code}: {err_msg}",
)
except httpx.TimeoutException:
return SendEmailResponse(
ok=False, message="❌ Timeout: Resend API non risponde. Riprova."
)
except Exception as exc:
_logger.error("send_email exception: %s", exc)
return SendEmailResponse(
ok=False, message=f"❌ Errore invio email: {str(exc)[:200]}"
)