9390eefa93
Los correos usaban un shell inline distinto en cada remitente. Ahora salen todos de una sola maqueta, la de security_token, portada de las plantillas de Django. De las 7 de Django solo security_token y activation traían el diseño; las otras 5 eran HTML soso. Las plantillas de Django eran un volcado del DOM de Gmail. Al portarlas se quitó lo que no era diseño: los enlaces iban envueltos en el rastreador de Mailjet (079xk.mjt.lu), que además redirigía a ultimowow.com en vez de a nosotros; había un píxel de apertura, un "Click on me" oculto y atributos que mete Gmail al mostrar el correo. Las imágenes apuntaban al proxy de Gmail (ci3.googleusercontent.com), no a nuestro /static/, así que el diseño se rompía el día que Google dejara de servirlas. Ninguna URL va a fuego: todas cuelgan de SITE_URL, así que cambiar de dominio es tocar el .env. En un correo han de ser absolutas (se abre desde Gmail), así que se construyen a partir de esa variable. El logo nw-mail-logo.png daba 404: solo existía el .webp del tema, que es el de UltimoWoW (león negro + letras "UW"). El auténtico de NovaWoW, un león de acero sin letras, sobrevivía únicamente en la caché de Gmail y se recuperó de ahí (600x320 RGBA, íntegro). Verificado renderizando el original y el nuestro en un navegador y comparando: cuadran pixel a pixel. Eso destapó dos fallos que se corrigen aquí: faltaba la etiqueta <h1> de apertura, y el estilo de los párrafos de datos era el de la letra pequeña del pie (11px/17px) en vez del real (14px/21px). El activation de Django mandaba la contraseña en texto plano; aquí no se manda. mail.ts: leía EMAIL_USE_SSL pero ignoraba EMAIL_USE_TLS, así que en el puerto 587 el STARTTLS era oportunista y, si el servidor no lo ofrecía, nodemailer enviaba en claro. Ahora requireTLS lo hace obligatorio. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
38 lines
1.1 KiB
TypeScript
38 lines
1.1 KiB
TypeScript
import nodemailer from 'nodemailer'
|
|
|
|
// Transporte SMTP (mismas variables que Django; hoy: Proton Mail).
|
|
let transporter: nodemailer.Transporter | null = null
|
|
|
|
function getTransporter(): nodemailer.Transporter {
|
|
if (!transporter) {
|
|
transporter = nodemailer.createTransport({
|
|
host: process.env.EMAIL_HOST,
|
|
port: Number(process.env.EMAIL_PORT || 587),
|
|
secure: process.env.EMAIL_USE_SSL === 'True', // true para 465, false para 587 (STARTTLS)
|
|
// Sin esto el STARTTLS del 587 es OPORTUNISTA: si el servidor no lo
|
|
// ofrece, nodemailer manda en claro (y ahí van tokens y contraseñas).
|
|
requireTLS: process.env.EMAIL_USE_TLS === 'True',
|
|
auth: {
|
|
user: process.env.EMAIL_HOST_USER,
|
|
pass: process.env.EMAIL_HOST_PASSWORD,
|
|
},
|
|
})
|
|
}
|
|
return transporter
|
|
}
|
|
|
|
export async function sendMail(to: string, subject: string, html: string): Promise<boolean> {
|
|
try {
|
|
await getTransporter().sendMail({
|
|
from: process.env.EMAIL_HOST_USER,
|
|
to,
|
|
subject,
|
|
html,
|
|
})
|
|
return true
|
|
} catch (e) {
|
|
console.error('sendMail error:', e)
|
|
return false
|
|
}
|
|
}
|