melodix-api / app /utils /email.py
GitHub Action
deploy from github actions
440bac0
Raw
History Blame Contribute Delete
8.66 kB
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"""
<html>
<body style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #0d0e12; color: #f8f8ff; padding: 20px; margin: 0;">
<div style="max-width: 500px; margin: 0 auto; background-color: #15171e; border: 1px solid #232733; border-radius: 16px; padding: 30px; box-shadow: 0 8px 30px rgba(0,0,0,0.5);">
<div style="text-align: center; margin-bottom: 25px;">
<h2 style="color: #9d5cf6; margin: 0; font-size: 26px; letter-spacing: 4px; font-weight: 800;">MELODIX</h2>
<p style="color: #a0a0b8; margin: 5px 0 0 0; font-size: 13px;">Código de Restablecimiento</p>
</div>
<hr style="border: 0; border-top: 1px solid #232733; margin-bottom: 25px;">
<p style="font-size: 15px; line-height: 22px; color: #e2e2e9;">Hola,</p>
<p style="font-size: 15px; line-height: 22px; color: #e2e2e9;">Has solicitado restablecer la contraseña de tu cuenta de Melodix. Utiliza el siguiente código de verificación de 6 dígitos:</p>
<div style="text-align: center; margin: 30px 0;">
<span style="font-family: 'Courier New', Courier, monospace; font-size: 36px; font-weight: bold; letter-spacing: 8px; color: #00f5d4; background-color: rgba(0, 245, 212, 0.08); padding: 12px 24px; border-radius: 12px; border: 1px solid rgba(0, 245, 212, 0.25); display: inline-block;">
{code}
</span>
</div>
<p style="font-size: 13px; line-height: 18px; color: #a0a0b8;">Este código es válido por 15 minutos. Si no solicitaste este cambio, puedes ignorar este correo de forma segura.</p>
<hr style="border: 0; border-top: 1px solid #232733; margin-top: 30px; margin-bottom: 20px;">
<p style="font-size: 11px; text-align: center; color: #6c757d; margin: 0;">&copy; {datetime_year()} Melodix App. Todos los derechos reservados.</p>
</div>
</body>
</html>
"""
# 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