From 7fb16937175c279c55373c427bbd125768b614b1 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Tue, 4 Nov 2025 00:19:35 -0300 Subject: [PATCH] feat: enhance email notification system with tracking and feedback - Introduced a new feature to track email statuses by implementing a mapping of email IDs. - Added a query to fetch email statuses based on tracked IDs, improving the monitoring of email delivery. - Enhanced the logging system for email and chat notifications, providing detailed feedback on the sending process. - Implemented user feedback messages for various actions, improving the overall user experience. - Refactored the notification sending logic to support better error handling and status updates. --- .../(dashboard)/ti/notificacoes/+page.svelte | 415 ++++++++++++++++-- packages/backend/convex/email.ts | 19 + 2 files changed, 387 insertions(+), 47 deletions(-) diff --git a/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte index 5cb06fe..b162d03 100644 --- a/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte @@ -12,6 +12,16 @@ const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {}); const usuariosQuery = useQuery(api.usuarios.listar, {}); + // Mapa de emailIds para rastrear status + let emailIdsRastreados = $state>(new Set()); + + // Query para buscar status dos emails + const emailIdsArray = $derived(Array.from(emailIdsRastreados)); + const emailsStatusQuery = useQuery( + api.email.buscarEmailsPorIds, + emailIdsArray.length > 0 ? { emailIds: emailIdsArray as any[] } : undefined + ); + // Extrair dados das queries de forma robusta const templates = $derived.by(() => { if (templatesQuery === undefined || templatesQuery === null) { @@ -83,6 +93,21 @@ let criandoTemplates = $state(false); let progressoEnvio = $state({ total: 0, enviados: 0, falhas: 0 }); + // Estrutura de dados para logs de envio + type StatusLog = "sucesso" | "erro" | "fila" | "info" | "enviando"; + type TipoLog = "chat" | "email"; + type LogEnvio = { + timestamp: number; + tipo: TipoLog; + destinatario: string; + status: StatusLog; + mensagem: string; + emailId?: string; // Para emails, guardar o ID para rastrear status + }; + + let logsEnvio = $state([]); + let terminalScrollRef: HTMLDivElement | null = $state(null); + // Estados para agendamento let agendarEnvio = $state(false); let dataAgendamento = $state(""); @@ -120,10 +145,124 @@ let corpoTemplate = $state(""); let variaveisTemplate = $state(""); let criandoNovoTemplate = $state(false); + + // Estado para mensagens de feedback + let mensagem = $state<{ tipo: "success" | "error" | "info"; texto: string } | null>(null); const templateSelecionado = $derived( templates.find(t => t._id === templateId) ); + + // Função para mostrar mensagens + function mostrarMensagem(tipo: "success" | "error" | "info", texto: string) { + mensagem = { tipo, texto }; + setTimeout(() => { + mensagem = null; + }, 5000); + } + + // Função para adicionar log ao terminal + function adicionarLog(tipo: TipoLog, destinatario: string, status: StatusLog, mensagem: string, emailId?: string) { + logsEnvio = [...logsEnvio, { + timestamp: Date.now(), + tipo, + destinatario, + status, + mensagem, + emailId, + }]; + + // Adicionar emailId ao rastreamento se fornecido + if (emailId) { + emailIdsRastreados = new Set([...emailIdsRastreados, emailId]); + } + + // Auto-scroll para o final + setTimeout(() => { + if (terminalScrollRef) { + terminalScrollRef.scrollTop = terminalScrollRef.scrollHeight; + } + }, 10); + } + + // Atualizar logs quando status dos emails mudar + $effect(() => { + if (!emailsStatusQuery || emailsStatusQuery === undefined) return; + + // Extrair dados da query + const emails = Array.isArray(emailsStatusQuery) + ? emailsStatusQuery + : "data" in emailsStatusQuery && Array.isArray(emailsStatusQuery.data) + ? emailsStatusQuery.data + : []; + + if (emails.length === 0) return; + + for (const email of emails) { + if (!email) continue; + + // Encontrar logs relacionados a este email + const logsRelacionados = logsEnvio.filter(log => log.emailId === email._id); + + for (const log of logsRelacionados) { + // Verificar se o status mudou + const novoStatus: StatusLog = + email.status === "enviado" ? "sucesso" : + email.status === "falha" ? "erro" : + email.status === "enviando" ? "enviando" : + email.status === "pendente" ? "fila" : + "info"; + + // Se o status mudou, atualizar o log + if (log.status !== novoStatus) { + const indice = logsEnvio.findIndex(l => l === log); + if (indice !== -1) { + const novaMensagem = + email.status === "enviado" ? "Email enviado com sucesso" : + email.status === "falha" ? `Falha ao enviar: ${email.erroDetalhes || "Erro desconhecido"}` : + email.status === "enviando" ? "Enviando email..." : + email.status === "pendente" ? "Email em fila de envio" : + log.mensagem; + + logsEnvio = logsEnvio.map((l, i) => + i === indice + ? { ...l, status: novoStatus, mensagem: novaMensagem } + : l + ); + } + } + } + } + }); + + // Função para limpar logs + function limparLogs() { + logsEnvio = []; + emailIdsRastreados = new Set(); + } + + // Função para formatar timestamp + function formatarTimestamp(timestamp: number): string { + return format(new Date(timestamp), "HH:mm:ss", { locale: ptBR }); + } + + // Função para obter cor do status + function obterCorStatus(status: StatusLog): string { + switch (status) { + case "sucesso": + return "text-success"; + case "erro": + return "text-error"; + case "fila": + return "text-warning"; + case "enviando": + return "text-info"; + case "info": + return "text-info"; + default: + return "text-base-content"; + } + } async function criarTemplatesPadrao() { if (criandoTemplates) return; @@ -132,14 +271,16 @@ try { const resultado = await client.mutation(api.templatesMensagens.criarTemplatesPadrao, {}); if (resultado.sucesso) { - alert("✅ Templates padrão criados com sucesso! A página será recarregada."); - window.location.reload(); + mostrarMensagem("success", "Templates padrão criados com sucesso! A página será recarregada."); + setTimeout(() => { + window.location.reload(); + }, 2000); } else { - alert("❌ Erro ao criar templates padrão."); + mostrarMensagem("error", "Erro ao criar templates padrão."); } } catch (error: any) { console.error("Erro ao criar templates:", error); - alert("❌ Erro ao criar templates: " + (error.message || "Erro desconhecido")); + mostrarMensagem("error", "Erro ao criar templates: " + (error.message || "Erro desconhecido")); } finally { criandoTemplates = false; } @@ -161,25 +302,25 @@ async function salvarNovoTemplate() { if (!authStore.usuario) { - alert("❌ Você precisa estar autenticado para criar templates."); + mostrarMensagem("error", "Você precisa estar autenticado para criar templates."); return; } // Validações if (!codigoTemplate.trim()) { - alert("❌ O código do template é obrigatório."); + mostrarMensagem("error", "O código do template é obrigatório."); return; } if (!nomeTemplate.trim()) { - alert("❌ O nome do template é obrigatório."); + mostrarMensagem("error", "O nome do template é obrigatório."); return; } if (!tituloTemplate.trim()) { - alert("❌ O título do template é obrigatório."); + mostrarMensagem("error", "O título do template é obrigatório."); return; } if (!corpoTemplate.trim()) { - alert("❌ O corpo do template é obrigatório."); + mostrarMensagem("error", "O corpo do template é obrigatório."); return; } @@ -201,16 +342,18 @@ }); if (resultado.sucesso) { - alert("✅ Template criado com sucesso!"); + mostrarMensagem("success", "Template criado com sucesso!"); fecharModalNovoTemplate(); // Recarregar a página para atualizar a lista - window.location.reload(); + setTimeout(() => { + window.location.reload(); + }, 1500); } else { - alert("❌ Erro ao criar template: " + (resultado.erro || "Erro desconhecido")); + mostrarMensagem("error", "Erro ao criar template: " + (resultado.erro || "Erro desconhecido")); } } catch (error: any) { console.error("Erro ao criar template:", error); - alert("❌ Erro ao criar template: " + (error.message || "Erro desconhecido")); + mostrarMensagem("error", "Erro ao criar template: " + (error.message || "Erro desconhecido")); } finally { criandoNovoTemplate = false; } @@ -218,17 +361,17 @@ async function enviarNotificacao() { if (!enviarParaTodos && !destinatarioId) { - alert("Selecione um destinatário ou marque 'Enviar para todos'"); + mostrarMensagem("error", "Selecione um destinatário ou marque 'Enviar para todos'"); return; } if (usarTemplate && !templateId) { - alert("Selecione um template"); + mostrarMensagem("error", "Selecione um template"); return; } if (!usarTemplate && !mensagemPersonalizada.trim()) { - alert("Digite uma mensagem"); + mostrarMensagem("error", "Digite uma mensagem"); return; } @@ -236,19 +379,19 @@ let agendadaPara: number | undefined = undefined; if (agendarEnvio) { if (!dataAgendamento || !horaAgendamento) { - alert("Preencha a data e hora para agendamento"); + mostrarMensagem("error", "Preencha a data e hora para agendamento"); return; } try { const dataHora = new Date(`${dataAgendamento}T${horaAgendamento}`); if (dataHora.getTime() <= Date.now()) { - alert("A data e hora devem ser futuras"); + mostrarMensagem("error", "A data e hora devem ser futuras"); return; } agendadaPara = dataHora.getTime(); } catch (error) { - alert("Data ou hora inválida"); + mostrarMensagem("error", "Data ou hora inválida"); return; } } @@ -256,6 +399,10 @@ processando = true; progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; + // Limpar logs anteriores quando iniciar novo envio + logsEnvio = []; + emailIdsRastreados = new Set(); + try { // Obter lista de destinatários const destinatarios: typeof usuarios = enviarParaTodos @@ -263,11 +410,19 @@ : usuarios.filter(u => u._id === destinatarioId); if (destinatarios.length === 0) { - alert("Nenhum destinatário encontrado"); + adicionarLog("email", "Sistema", "erro", "Nenhum destinatário encontrado"); + mostrarMensagem("error", "Nenhum destinatário encontrado"); return; } progressoEnvio.total = destinatarios.length; + + // Log inicial + const tipoMensagem = usarTemplate ? `Template: ${templateSelecionado?.nome || ""}` : "Mensagem personalizada"; + const destinatariosText = enviarParaTodos + ? `Todos os usuários (${destinatarios.length})` + : destinatarios.map(d => d.nome).join(", "); + adicionarLog("email", "Sistema", "info", `Iniciando envio de notificação via ${canal} para ${destinatariosText} - ${tipoMensagem}`); // Se for envio para um único usuário if (destinatarios.length === 1) { @@ -278,6 +433,7 @@ // ENVIAR PARA CHAT if (canal === "chat" || canal === "ambos") { try { + adicionarLog("chat", destinatario.nome, "enviando", "Criando/buscando conversa..."); const conversaResult = await client.mutation( api.chat.criarOuBuscarConversaIndividual, { outroUsuarioId: destinatario._id as any } @@ -290,23 +446,31 @@ if (agendadaPara) { // Agendar mensagem + adicionarLog("chat", destinatario.nome, "info", "Agendando mensagem..."); resultadoChat = await client.mutation(api.chat.agendarMensagem, { conversaId: conversaResult.conversaId, conteudo: mensagem, agendadaPara: agendadaPara, }); + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + adicionarLog("chat", destinatario.nome, "sucesso", `Mensagem agendada para ${dataFormatada}`); } else { // Envio imediato + adicionarLog("chat", destinatario.nome, "enviando", "Enviando mensagem..."); resultadoChat = await client.mutation(api.chat.enviarMensagem, { conversaId: conversaResult.conversaId, conteudo: mensagem, tipo: "texto", permitirNotificacaoParaSiMesmo: true, }); + adicionarLog("chat", destinatario.nome, "sucesso", "Mensagem enviada com sucesso"); } + } else { + adicionarLog("chat", destinatario.nome, "erro", "Falha ao criar/buscar conversa"); } - } catch (error) { + } catch (error: any) { console.error("Erro ao enviar chat:", error); + adicionarLog("chat", destinatario.nome, "erro", `Erro: ${error.message || "Erro desconhecido"}`); } } @@ -314,6 +478,7 @@ if (canal === "email" || canal === "ambos") { if (destinatario.email) { try { + adicionarLog("email", destinatario.nome, "enviando", `Enfileirando email para ${destinatario.email}...`); if (usarTemplate && templateId) { const template = templateSelecionado; if (template) { @@ -328,6 +493,18 @@ enviadoPorId: authStore.usuario._id as Id<"usuarios">, agendadaPara: agendadaPara, }); + if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + adicionarLog("email", destinatario.nome, "fila", `Email agendado para ${dataFormatada}`, resultadoEmail.emailId); + } else { + adicionarLog("email", destinatario.nome, "fila", "Email enfileirado para envio", resultadoEmail.emailId); + } + } else { + adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + } + } else { + adicionarLog("email", destinatario.nome, "erro", "Template não encontrado"); } } else { resultadoEmail = await client.mutation(api.email.enfileirarEmail, { @@ -338,37 +515,50 @@ enviadoPorId: authStore.usuario._id as Id<"usuarios">, agendadaPara: agendadaPara, }); + if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + adicionarLog("email", destinatario.nome, "fila", `Email agendado para ${dataFormatada}`, resultadoEmail.emailId); + } else { + adicionarLog("email", destinatario.nome, "fila", "Email enfileirado para envio", resultadoEmail.emailId); + } + } else { + adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + } } - } catch (error) { + } catch (error: any) { console.error("Erro ao enviar email:", error); + adicionarLog("email", destinatario.nome, "erro", `Erro: ${error.message || "Erro desconhecido"}`); } + } else { + adicionarLog("email", destinatario.nome, "erro", "Destinatário não possui email cadastrado"); } } // Feedback de sucesso - let mensagem = agendadaPara - ? `✅ Notificação agendada com sucesso!` + let mensagemSucesso = agendadaPara + ? `Notificação agendada com sucesso!` : "Notificação enviada com sucesso!"; if (agendadaPara) { const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - mensagem += `\n\nSerá enviada em: ${dataFormatada}`; + mensagemSucesso += ` Será enviada em: ${dataFormatada}`; } else { if (canal === "ambos") { if (resultadoChat && resultadoEmail) { - mensagem = "✅ Notificação enviada para Chat e Email!"; + mensagemSucesso = "Notificação enviada para Chat e Email!"; } else if (resultadoChat) { - mensagem = "✅ Notificação enviada para Chat. Email falhou."; + mensagemSucesso = "Notificação enviada para Chat. Email falhou."; } else if (resultadoEmail) { - mensagem = "✅ Notificação enviada para Email. Chat falhou."; + mensagemSucesso = "Notificação enviada para Email. Chat falhou."; } } else if (canal === "chat" && resultadoChat) { - mensagem = "✅ Mensagem enviada no Chat!"; + mensagemSucesso = "Mensagem enviada no Chat!"; } else if (canal === "email" && resultadoEmail) { - mensagem = "✅ Email enfileirado para envio!"; + mensagemSucesso = "Email enfileirado para envio!"; } } - alert(mensagem); + mostrarMensagem("success", mensagemSucesso); progressoEnvio.enviados = 1; } else { // ENVIO EM MASSA @@ -377,11 +567,14 @@ let falhasChat = 0; let falhasEmail = 0; + adicionarLog("email", "Sistema", "info", `Processando ${destinatarios.length} destinatários...`); + for (const destinatario of destinatarios) { try { // ENVIAR PARA CHAT if (canal === "chat" || canal === "ambos") { try { + adicionarLog("chat", destinatario.nome, "enviando", "Processando..."); const conversaResult = await client.mutation( api.chat.criarOuBuscarConversaIndividual, { outroUsuarioId: destinatario._id as any } @@ -400,6 +593,8 @@ conteudo: mensagem, agendadaPara: agendadaPara, }); + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + adicionarLog("chat", destinatario.nome, "sucesso", `Agendado para ${dataFormatada}`); } else { await client.mutation(api.chat.enviarMensagem, { conversaId: conversaResult.conversaId, @@ -407,13 +602,16 @@ tipo: "texto", permitirNotificacaoParaSiMesmo: true, }); + adicionarLog("chat", destinatario.nome, "sucesso", "Enviado com sucesso"); } sucessosChat++; } else { + adicionarLog("chat", destinatario.nome, "erro", "Falha ao criar/buscar conversa"); falhasChat++; } - } catch (error) { + } catch (error: any) { console.error(`Erro ao enviar chat para ${destinatario.nome}:`, error); + adicionarLog("chat", destinatario.nome, "erro", `Erro: ${error.message || "Erro desconhecido"}`); falhasChat++; } } @@ -422,10 +620,11 @@ if (canal === "email" || canal === "ambos") { if (destinatario.email) { try { + adicionarLog("email", destinatario.nome, "enviando", `Enfileirando email para ${destinatario.email}...`); if (usarTemplate && templateId) { const template = templateSelecionado; if (template) { - await client.mutation(api.email.enviarEmailComTemplate, { + const resultadoEmail = await client.mutation(api.email.enviarEmailComTemplate, { destinatario: destinatario.email, destinatarioId: destinatario._id as any, templateCodigo: template.codigo, @@ -436,12 +635,24 @@ enviadoPorId: authStore.usuario._id as Id<"usuarios">, agendadaPara: agendadaPara, }); - sucessosEmail++; + if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + adicionarLog("email", destinatario.nome, "fila", `Agendado para ${dataFormatada}`, resultadoEmail.emailId); + } else { + adicionarLog("email", destinatario.nome, "fila", "Enfileirado para envio", resultadoEmail.emailId); + } + sucessosEmail++; + } else { + adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + falhasEmail++; + } } else { + adicionarLog("email", destinatario.nome, "erro", "Template não encontrado"); falhasEmail++; } } else { - await client.mutation(api.email.enfileirarEmail, { + const resultadoEmail = await client.mutation(api.email.enfileirarEmail, { destinatario: destinatario.email, destinatarioId: destinatario._id as any, assunto: "Notificação do Sistema", @@ -449,13 +660,26 @@ enviadoPorId: authStore.usuario._id as Id<"usuarios">, agendadaPara: agendadaPara, }); - sucessosEmail++; + if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { + if (agendadaPara) { + const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + adicionarLog("email", destinatario.nome, "fila", `Agendado para ${dataFormatada}`, resultadoEmail.emailId); + } else { + adicionarLog("email", destinatario.nome, "fila", "Enfileirado para envio", resultadoEmail.emailId); + } + sucessosEmail++; + } else { + adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + falhasEmail++; + } } - } catch (error) { + } catch (error: any) { console.error(`Erro ao enviar email para ${destinatario.nome}:`, error); + adicionarLog("email", destinatario.nome, "erro", `Erro: ${error.message || "Erro desconhecido"}`); falhasEmail++; } } else { + adicionarLog("email", destinatario.nome, "erro", "Destinatário não possui email cadastrado"); falhasEmail++; } } @@ -468,25 +692,27 @@ } // Feedback de envio em massa - let mensagem = agendadaPara - ? `✅ Agendamento em massa concluído!\n\n` - : `✅ Envio em massa concluído!\n\n`; + let mensagemMassa = agendadaPara + ? `Agendamento em massa concluído! ` + : `Envio em massa concluído! `; if (agendadaPara) { const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - mensagem += `Será enviado em: ${dataFormatada}\n\n`; + mensagemMassa += `Será enviado em: ${dataFormatada}. `; } if (canal === "ambos") { - mensagem += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas\n`; - mensagem += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas`; + mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas. `; + mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas.`; } else if (canal === "chat") { - mensagem += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas`; + mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas.`; } else if (canal === "email") { - mensagem += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas`; + mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas.`; } - alert(mensagem); + // Adicionar log resumo + adicionarLog("email", "Sistema", "info", mensagemMassa); + mostrarMensagem("success", mensagemMassa); } // Limpar form @@ -499,10 +725,12 @@ horaAgendamento = ""; } catch (error: any) { console.error("Erro ao enviar notificação:", error); - alert("Erro ao enviar notificação: " + (error.message || "Erro desconhecido")); + adicionarLog("email", "Sistema", "erro", `Erro geral: ${error.message || "Erro desconhecido"}`); + mostrarMensagem("error", "Erro ao enviar notificação: " + (error.message || "Erro desconhecido")); } finally { processando = false; progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; + adicionarLog("email", "Sistema", "info", "Processo de envio finalizado"); } } @@ -523,6 +751,54 @@ + + {#if mensagem} +
+ + {#if mensagem.tipo === "success"} + + {:else if mensagem.tipo === "error"} + + {:else} + + {/if} + + {mensagem.texto} + +
+ {/if} +
@@ -772,6 +1048,51 @@ {/if}
+ + +
+
+ + {#if logsEnvio.length > 0} + + {/if} +
+
+ {#if logsEnvio.length === 0} +
+ Aguardando envio de notificação... +
+ {:else} + {#each logsEnvio as log} +
+ [{formatarTimestamp(log.timestamp)}] + + {log.tipo.toUpperCase()} + + {log.destinatario}: + + {log.mensagem} + +
+ {/each} + {/if} +
+
diff --git a/packages/backend/convex/email.ts b/packages/backend/convex/email.ts index 3e622ff..587ffba 100644 --- a/packages/backend/convex/email.ts +++ b/packages/backend/convex/email.ts @@ -218,6 +218,25 @@ export const getEmailById = internalQuery({ }, }); +/** + * Buscar emails por IDs (query pública) + */ +export const buscarEmailsPorIds = query({ + args: { + emailIds: v.array(v.id("notificacoesEmail")), + }, + handler: async (ctx, args) => { + const emails = []; + for (const emailId of args.emailIds) { + const email = await ctx.db.get(emailId); + if (email) { + emails.push(email); + } + } + return emails; + }, +}); + export const getActiveEmailConfig = internalQuery({ args: {}, // Tipo inferido automaticamente pelo Convex