"use node"; import { action } from "../_generated/server"; import { v } from "convex/values"; import { internal } from "../_generated/api"; import { decryptSMTPPasswordNode } from "./utils/nodeCrypto"; import nodemailer from "nodemailer"; export const enviar = action({ args: { emailId: v.id("notificacoesEmail"), }, returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }), handler: async (ctx, args) => { "use node"; let email; try { // Buscar email da fila email = await ctx.runQuery(internal.email.getEmailById, { emailId: args.emailId, }); if (!email) { return { sucesso: false, erro: "Email não encontrado" }; } // Buscar configuração SMTP ativa const configRaw = await ctx.runQuery( internal.email.getActiveEmailConfig, {} ); if (!configRaw) { console.error( "❌ Configuração SMTP não encontrada ou inativa para email:", email.destinatario ); return { sucesso: false, erro: "Configuração de email não encontrada ou inativa. Verifique as configurações SMTP no painel de TI.", }; } console.log("📧 Tentando enviar email:", { para: email.destinatario, assunto: email.assunto, servidor: configRaw.servidor, porta: configRaw.porta, }); // Descriptografar senha usando função compatível com Node.js let senhaDescriptografada: string; try { senhaDescriptografada = await decryptSMTPPasswordNode( configRaw.senhaHash ); } catch (decryptError) { const decryptErrorMessage = decryptError instanceof Error ? decryptError.message : String(decryptError); console.error( "Erro ao descriptografar senha SMTP:", decryptErrorMessage ); return { sucesso: false, erro: `Erro ao descriptografar senha SMTP: ${decryptErrorMessage}`, }; } const config = { ...configRaw, senha: senhaDescriptografada, }; // Config já foi validado acima // Avisar mas não bloquear se não foi testado if (!config.testadoEm) { console.warn( "⚠️ Configuração SMTP não foi testada. Tentando enviar mesmo assim..." ); } // Marcar como enviando await ctx.runMutation(internal.email.markEmailEnviando, { emailId: args.emailId, }); // Criar transporter do nodemailer com configuração melhorada const transporterOptions: { host: string; port: number; secure: boolean; requireTLS?: boolean; auth: { user: string; pass: string; }; tls?: { rejectUnauthorized: boolean; ciphers?: string; }; connectionTimeout: number; greetingTimeout: number; socketTimeout: number; pool?: boolean; maxConnections?: number; maxMessages?: number; } = { host: config.servidor, port: config.porta, secure: config.usarSSL, auth: { user: config.usuario, pass: config.senha, // Senha já descriptografada }, connectionTimeout: 15000, // 15 segundos greetingTimeout: 15000, socketTimeout: 15000, pool: true, // Usar pool de conexões maxConnections: 5, maxMessages: 100, }; // Adicionar TLS apenas se necessário if (config.usarTLS) { transporterOptions.requireTLS = true; transporterOptions.tls = { rejectUnauthorized: false, // Permitir certificados autoassinados }; } else if (config.usarSSL) { transporterOptions.tls = { rejectUnauthorized: false, }; } const transporter = nodemailer.createTransport(transporterOptions); // Verificar conexão antes de enviar try { await transporter.verify(); console.log("✅ Conexão SMTP verificada com sucesso"); } catch (verifyError) { const verifyErrorMessage = verifyError instanceof Error ? verifyError.message : String(verifyError); console.warn( "⚠️ Falha na verificação SMTP, mas tentando enviar mesmo assim:", verifyErrorMessage ); // Não bloquear envio por falha na verificação, apenas avisar } // Validar email destinatário antes de enviar const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email.destinatario)) { throw new Error(`Email destinatário inválido: ${email.destinatario}`); } // Criar versão texto do HTML (remover tags e decodificar entidades básicas) const textoPlano = email.corpo .replace(/<[^>]*>/g, "") // Remover tags HTML .replace(/ /g, " ") .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'/g, "'") .trim(); // Enviar email const info = await transporter.sendMail({ from: `"${config.nomeRemetente}" <${config.emailRemetente}>`, to: email.destinatario, subject: email.assunto, html: email.corpo, text: textoPlano || email.assunto, // Versão texto para clientes que não suportam HTML headers: { "X-Mailer": "SGSE-Sistema-de-Gerenciamento-de-Secretaria", "X-Priority": "3", }, }); interface MessageInfo { messageId?: string; response?: string; } const messageInfo = info as MessageInfo; console.log("✅ Email enviado com sucesso!", { para: email.destinatario, assunto: email.assunto, messageId: messageInfo.messageId, response: messageInfo.response, }); // Marcar como enviado await ctx.runMutation(internal.email.markEmailEnviado, { emailId: args.emailId, }); return { sucesso: true }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const errorStack = error instanceof Error ? error.stack : undefined; console.error("❌ Erro ao enviar email:", { emailId: args.emailId, destinatario: email?.destinatario, erro: errorMessage, stack: errorStack, }); // Marcar como falha com detalhes completos const erroCompleto = errorStack ? `${errorMessage}\n\nStack: ${errorStack}` : errorMessage; await ctx.runMutation(internal.email.markEmailFalha, { emailId: args.emailId, erro: erroCompleto.substring(0, 2000), // Limitar tamanho do erro }); return { sucesso: false, erro: errorMessage }; } }, });