import { join } from "node:path"; import type { PlatformDb } from "./platform_db.ts"; import { config, ROOT } from "./config.ts"; import { resolveSmtp, smtpConfigured as smtpConfiguredFromDb } from "./smtp.ts"; export type MailAttachment = { filename: string; content: Uint8Array; cid: string; contentType?: string; }; export type MailPayload = { to: string; subject: string; text: string; html?: string; attachments?: MailAttachment[]; }; export async function smtpConfigured(platformDb: PlatformDb): Promise { return await smtpConfiguredFromDb(platformDb); } /** Envía correo por SMTP. Si no hay SMTP, no falla: sent=false. */ export async function sendMail( platformDb: PlatformDb, payload: MailPayload, ): Promise<{ sent: boolean; error?: string; message_id?: string }> { if (!await smtpConfigured(platformDb)) { return { sent: false, error: "SMTP no configurado o deshabilitado" }; } const s = await resolveSmtp(platformDb); const from = (s.from_address ?? "").trim(); if (!from || !/(?:^|<)[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+(?:>|$)/.test(from)) { return { sent: false, error: "Remitente (From) inválido o vacío. Debe ser un correo, ej. PANELS ", }; } try { const nodemailer = await import("npm:nodemailer@6.9.16"); const transporter = nodemailer.createTransport({ host: s.host, port: s.port, secure: s.port === 465, requireTLS: s.port === 587, auth: s.username ? { user: s.username, pass: s.password } : undefined, connectionTimeout: 20000, greetingTimeout: 20000, socketTimeout: 30000, tls: { rejectUnauthorized: false, servername: s.host, }, }); const info = await transporter.sendMail({ from, to: payload.to, subject: payload.subject, text: payload.text, html: payload.html, attachments: payload.attachments?.map((a) => ({ filename: a.filename, content: a.content, cid: a.cid, contentType: a.contentType ?? "image/png", contentDisposition: "inline" as const, })), }); const messageId = typeof info?.messageId === "string" ? info.messageId : undefined; console.log(`[smtp] sent to=${payload.to} id=${messageId ?? "—"} host=${s.host}:${s.port}`); return { sent: true, message_id: messageId }; } catch (e) { const msg = e instanceof Error ? e.message : "Error al enviar correo"; console.error(`[smtp] fail to=${payload.to}:`, msg); return { sent: false, error: msg }; } } /** Prueba de conexión / envío a un destinatario. */ export async function testSmtp( platformDb: PlatformDb, to: string, ): Promise<{ sent: boolean; error?: string; message_id?: string }> { const dest = (to ?? "").trim(); if (!dest || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(dest)) { return { sent: false, error: "Indique un correo de prueba válido" }; } return sendMail(platformDb, { to: dest, subject: "PANELS — prueba SMTP", text: `Este es un correo de prueba de la configuración SMTP de PANELS. Si recibió este mensaje, el SMTP está funcionando. Enviado: ${new Date().toISOString()} Destinatario: ${dest} `, }); } function escapeHtml(value: string): string { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } export type AccessEmailOpts = { companyName: string; username: string; password: string; contactName?: string; }; const LOGO_CID = "panels-logo@panels"; export function accessEmailAttachments(): MailAttachment[] { const path = join(ROOT, "api", "assets", "email", "logo.png"); try { const content = Deno.readFileSync(path); return [{ filename: "logo.png", content, cid: LOGO_CID, contentType: "image/png", }]; } catch { console.warn("[mail] logo.png no encontrado en", path); return []; } } export function accessEmailBody(opts: AccessEmailOpts): string { const hi = opts.contactName ? `Hola, ${opts.contactName}` : "Hola"; return `${hi} Tu acceso a PANELS está listo. La empresa ${opts.companyName} ya tiene acceso a PANELS. Utiliza los siguientes datos para iniciar sesión: Usuario administrador: ${opts.username} Contraseña temporal: ${opts.password} Ingresar: ${config.panelLoginUrl} Por seguridad, al ingresar por primera vez deberás crear una nueva contraseña. — Equipo PANELS Todas tus operaciones. Un solo sistema. `; } /** HTML del correo de acceso (diseño original + logo real por CID). */ export function accessEmailHtml(opts: AccessEmailOpts): string { const name = (opts.contactName ?? "").trim(); const hi = name ? `Hola, ${escapeHtml(name)}` : "Hola"; const company = escapeHtml(opts.companyName); const username = escapeHtml(opts.username); const password = escapeHtml(opts.password); const loginUrl = escapeHtml(config.panelLoginUrl); const mark = `PANELS`; const watermark = `
 
 
 
 
`; const footerMark = `
 
 
 
 
`; return ` Tu acceso a PANELS está listo
${mark} PANELS ${watermark}

${hi}

Tu acceso a PANELS está listo

La empresa ${company} ya tiene acceso a PANELS. Utiliza los siguientes datos para iniciar sesión:

👤 Usuario administrador | ${username}
 
🔒 Contraseña temporal | ${password}
Ingresar a PANELS

${loginUrl}

🛡 Por seguridad, al ingresar por primera vez deberás crear una nueva contraseña.
— Equipo PANELS
Todas tus operaciones. Un solo sistema.
${footerMark}

Este correo fue enviado automáticamente. No respondas a este mensaje.

`; }