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