'use node'; import { v } from 'convex/values'; import nodemailer from 'nodemailer'; import { internal } from '../_generated/api'; import { action } from '../_generated/server'; import { decryptSMTPPasswordNode } from './utils/nodeCrypto'; 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 }; } } });