import os
import smtplib
import logging
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
logger = logging.getLogger(__name__)
def send_recovery_email(to_email: str, code: str, raise_on_error: bool = False):
"""
Envía un correo electrónico con el código de recuperación de contraseña de 6 dígitos.
Soporta Brevo API (ideal para enviar a cualquier remitente gratis sin dominio),
Resend API (requiere dominio o enviar solo al creador), o SMTP tradicional como fallback.
"""
brevo_api_key = os.getenv("BREVO_API_KEY")
resend_api_key = os.getenv("RESEND_API_KEY")
smtp_from = os.getenv("SMTP_FROM")
smtp_user = os.getenv("SMTP_USER")
if smtp_from: smtp_from = smtp_from.strip("'\" ")
if smtp_user: smtp_user = smtp_user.strip("'\" ")
# HTML estilizado y profesional
html_content = f"""
MELODIX
Código de Restablecimiento
Hola,
Has solicitado restablecer la contraseña de tu cuenta de Melodix. Utiliza el siguiente código de verificación de 6 dígitos:
{code}
Este código es válido por 15 minutos. Si no solicitaste este cambio, puedes ignorar este correo de forma segura.
© {datetime_year()} Melodix App. Todos los derechos reservados.
"""
# 1. ENVIAR POR BREVO API (SI BREVO_API_KEY ESTÁ CONFIGURADA)
if brevo_api_key:
brevo_api_key = brevo_api_key.strip("'\" ")
try:
import requests
url = "https://api.brevo.com/v3/smtp/email"
headers = {
"api-key": brevo_api_key,
"Content-Type": "application/json"
}
# El remitente debe ser el verificado en Brevo (típicamente smtp_user)
sender_email = smtp_user or "mifemmnic@gmail.com"
sender_name = "Melodix"
if smtp_from:
if "<" in smtp_from and ">" in smtp_from:
parts = smtp_from.split("<")
sender_name = parts[0].strip()
sender_email = parts[1].replace(">", "").strip()
elif "@" in smtp_from:
sender_email = smtp_from.strip()
payload = {
"sender": {
"name": sender_name,
"email": sender_email
},
"to": [
{
"email": to_email
}
],
"subject": "Recuperación de Contraseña - Melodix",
"htmlContent": html_content
}
res = requests.post(url, json=payload, headers=headers, timeout=15)
if res.status_code in (200, 201, 202):
logger.info(f"Correo de recuperación enviado con éxito a {to_email} vía Brevo API")
return True
else:
err_msg = f"Brevo API retornó error {res.status_code}: {res.text}"
logger.error(err_msg)
if raise_on_error:
raise Exception(err_msg)
return False
except Exception as e:
logger.error(f"Error enviando correo vía Brevo a {to_email}: {e}", exc_info=True)
if raise_on_error:
raise e
return False
# 2. ENVIAR POR RESEND API (SI RESEND_API_KEY ESTÁ CONFIGURADA)
if resend_api_key:
resend_api_key = resend_api_key.strip("'\" ")
try:
import requests
url = "https://api.resend.com/emails"
headers = {
"Authorization": f"Bearer {resend_api_key}",
"Content-Type": "application/json"
}
from_addr = "onboarding@resend.dev"
if smtp_from and "@" in smtp_from:
domain = smtp_from.split("@")[-1].replace(">", "").strip()
if domain not in ["gmail.com", "outlook.com", "hotmail.com", "yahoo.com"]:
from_addr = smtp_from
if not from_addr.startswith("Melodix"):
from_addr = f"Melodix <{from_addr}>"
payload = {
"from": from_addr,
"to": [to_email],
"subject": "Recuperación de Contraseña - Melodix",
"html": html_content
}
res = requests.post(url, json=payload, headers=headers, timeout=15)
if res.status_code in (200, 201):
logger.info(f"Correo de recuperación enviado con éxito a {to_email} vía Resend API")
return True
else:
err_msg = f"Resend API retornó error {res.status_code}: {res.text}"
logger.error(err_msg)
if raise_on_error:
raise Exception(err_msg)
return False
except Exception as e:
logger.error(f"Error enviando correo vía Resend a {to_email}: {e}", exc_info=True)
if raise_on_error:
raise e
return False
# 2. ENVIAR POR SMTP TRADICIONAL (FALLBACK)
smtp_host = os.getenv("SMTP_HOST")
smtp_port = os.getenv("SMTP_PORT")
smtp_user = os.getenv("SMTP_USER")
smtp_password = os.getenv("SMTP_PASSWORD")
# Limpiar comillas iniciales/finales y espacios (común al pegar variables en Render)
if smtp_host: smtp_host = smtp_host.strip("'\" ")
if smtp_port: smtp_port = smtp_port.strip("'\" ")
if smtp_user: smtp_user = smtp_user.strip("'\" ")
if smtp_password: smtp_password = smtp_password.strip("'\" ")
if not smtp_from:
smtp_from = f"Melodix <{smtp_user}>"
if not smtp_host or not smtp_port or not smtp_user or not smtp_password:
logger.warning(f"Configuración SMTP incompleta. host={smtp_host}, port={smtp_port}, user={smtp_user}. No se puede enviar correo.")
logger.info(f"[SIMULADO] Código de recuperación para {to_email}: {code}")
return False
try:
# Configurar el mensaje
msg = MIMEMultipart("alternative")
msg["Subject"] = "Recuperación de Contraseña - Melodix"
msg["From"] = smtp_from
msg["To"] = to_email
msg.attach(MIMEText(html_content, "html"))
# Conectar al servidor SMTP
port = int(smtp_port)
if port == 465:
server = smtplib.SMTP_SSL(smtp_host, port, timeout=15)
else:
server = smtplib.SMTP(smtp_host, port, timeout=15)
server.ehlo()
server.starttls()
server.ehlo()
server.login(smtp_user, smtp_password)
server.sendmail(smtp_user, [to_email], msg.as_string())
server.quit()
logger.info(f"Correo de recuperación enviado con éxito a {to_email} vía SMTP")
return True
except Exception as e:
logger.error(f"Error enviando correo SMTP a {to_email}: {e}", exc_info=True)
if raise_on_error:
raise e
return False
def datetime_year():
from datetime import datetime
return datetime.utcnow().year