- Adiciona página /recuperar para solicitar redefinição de senha - Adiciona página /redefinir-senha para definir nova senha com token - Cria APIs /api/auth/forgot-password e /api/auth/reset-password - Adiciona notificação por e-mail quando ticket é criado - Repagina página de configurações removendo informações técnicas - Adiciona script de teste para todos os tipos de e-mail - Corrige acentuações em templates de e-mail 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
188 lines
6.5 KiB
TypeScript
188 lines
6.5 KiB
TypeScript
import * as React from "react"
|
|
import dotenv from "dotenv"
|
|
import { render } from "@react-email/render"
|
|
|
|
import { sendSmtpMail } from "@/server/email-smtp"
|
|
import AutomationEmail, { type AutomationEmailProps } from "../emails/automation-email"
|
|
import SimpleNotificationEmail, { type SimpleNotificationEmailProps } from "../emails/simple-notification-email"
|
|
|
|
dotenv.config({ path: ".env.local" })
|
|
dotenv.config({ path: ".env" })
|
|
|
|
function getSmtpConfig() {
|
|
const host = process.env.SMTP_HOST
|
|
const port = process.env.SMTP_PORT
|
|
const username = process.env.SMTP_USER
|
|
const password = process.env.SMTP_PASS
|
|
const fromEmail = process.env.SMTP_FROM_EMAIL
|
|
const fromName = process.env.SMTP_FROM_NAME ?? "Raven"
|
|
|
|
if (!host || !port || !username || !password || !fromEmail) return null
|
|
|
|
return {
|
|
host,
|
|
port: Number(port),
|
|
username,
|
|
password,
|
|
from: `"${fromName}" <${fromEmail}>`,
|
|
tls: process.env.SMTP_SECURE === "true",
|
|
rejectUnauthorized: false,
|
|
timeoutMs: 15000,
|
|
}
|
|
}
|
|
|
|
type EmailScenario = {
|
|
name: string
|
|
subject: string
|
|
render: () => Promise<string>
|
|
}
|
|
|
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://tickets.esdrasrenan.com.br"
|
|
|
|
const scenarios: EmailScenario[] = [
|
|
{
|
|
name: "Ticket Criado",
|
|
subject: "[TESTE] Novo chamado #41025 aberto",
|
|
render: async () => {
|
|
const props: SimpleNotificationEmailProps = {
|
|
title: "Novo chamado #41025 aberto",
|
|
message: "Seu chamado foi registrado com sucesso. Nossa equipe irá analisá-lo em breve.\n\nAssunto: Computador reiniciando sozinho\nPrioridade: Alta\nStatus: Pendente",
|
|
ctaLabel: "Ver chamado",
|
|
ctaUrl: `${baseUrl}/portal/tickets/test123`,
|
|
}
|
|
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
|
|
},
|
|
},
|
|
{
|
|
name: "Ticket Resolvido",
|
|
subject: "[TESTE] Chamado #41025 foi encerrado",
|
|
render: async () => {
|
|
const props: SimpleNotificationEmailProps = {
|
|
title: "Chamado #41025 encerrado",
|
|
message: "O chamado 'Computador reiniciando sozinho' foi marcado como concluído.\n\nCaso necessário, você pode responder pelo portal para reabrir dentro do prazo.",
|
|
ctaLabel: "Ver detalhes",
|
|
ctaUrl: `${baseUrl}/portal/tickets/test123`,
|
|
}
|
|
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
|
|
},
|
|
},
|
|
{
|
|
name: "Novo Comentário",
|
|
subject: "[TESTE] Atualização no chamado #41025",
|
|
render: async () => {
|
|
const props: SimpleNotificationEmailProps = {
|
|
title: "Nova atualização no seu chamado #41025",
|
|
message: "Um novo comentário foi adicionado ao chamado 'Computador reiniciando sozinho'.\n\nClique abaixo para visualizar e responder pelo portal.",
|
|
ctaLabel: "Abrir e responder",
|
|
ctaUrl: `${baseUrl}/portal/tickets/test123`,
|
|
}
|
|
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
|
|
},
|
|
},
|
|
{
|
|
name: "Automação - Mudança de Prioridade",
|
|
subject: "[TESTE] Prioridade alterada no chamado #41025",
|
|
render: async () => {
|
|
const props: AutomationEmailProps = {
|
|
title: "Prioridade alterada para Urgente",
|
|
message: "A prioridade do seu chamado foi alterada automaticamente pelo sistema.\n\nIsso pode ter ocorrido devido a regras de SLA ou categorização automática.",
|
|
ticket: {
|
|
reference: 41025,
|
|
subject: "Computador reiniciando sozinho",
|
|
companyName: "Paulicon Contabil",
|
|
status: "AWAITING_ATTENDANCE",
|
|
priority: "URGENT",
|
|
requesterName: "Renan",
|
|
assigneeName: "Administrador",
|
|
},
|
|
ctaLabel: "Ver chamado",
|
|
ctaUrl: `${baseUrl}/portal/tickets/test123`,
|
|
}
|
|
return render(<AutomationEmail {...props} />, { pretty: true })
|
|
},
|
|
},
|
|
{
|
|
name: "Automação - Atribuição de Agente",
|
|
subject: "[TESTE] Agente atribuído ao chamado #41025",
|
|
render: async () => {
|
|
const props: AutomationEmailProps = {
|
|
title: "Agente atribuído ao seu chamado",
|
|
message: "O agente Administrador foi automaticamente atribuído ao seu chamado e entrará em contato em breve.",
|
|
ticket: {
|
|
reference: 41025,
|
|
subject: "Computador reiniciando sozinho",
|
|
companyName: "Paulicon Contabil",
|
|
status: "AWAITING_ATTENDANCE",
|
|
priority: "HIGH",
|
|
requesterName: "Renan",
|
|
assigneeName: "Administrador",
|
|
},
|
|
ctaLabel: "Ver chamado",
|
|
ctaUrl: `${baseUrl}/portal/tickets/test123`,
|
|
}
|
|
return render(<AutomationEmail {...props} />, { pretty: true })
|
|
},
|
|
},
|
|
{
|
|
name: "Redefinição de Senha",
|
|
subject: "[TESTE] Redefinição de senha - Raven",
|
|
render: async () => {
|
|
const props: SimpleNotificationEmailProps = {
|
|
title: "Redefinição de Senha",
|
|
message: "Recebemos uma solicitação para redefinir a senha da sua conta.\n\nSe você não fez essa solicitação, pode ignorar este e-mail.\n\nEste link expira em 1 hora.",
|
|
ctaLabel: "Redefinir Senha",
|
|
ctaUrl: `${baseUrl}/redefinir-senha?token=abc123def456`,
|
|
}
|
|
return render(<SimpleNotificationEmail {...props} />, { pretty: true })
|
|
},
|
|
},
|
|
]
|
|
|
|
async function main() {
|
|
const targetEmail = process.argv[2] ?? "renan.pac@paulicon.com.br"
|
|
|
|
const smtp = getSmtpConfig()
|
|
if (!smtp) {
|
|
console.error("SMTP não configurado. Defina as variáveis SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM_EMAIL")
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log("=".repeat(60))
|
|
console.log("Teste de E-mails - Sistema de Chamados Raven")
|
|
console.log("=".repeat(60))
|
|
console.log(`\nDestinatario: ${targetEmail}`)
|
|
console.log(`SMTP: ${smtp.host}:${smtp.port}`)
|
|
console.log(`De: ${smtp.from}`)
|
|
console.log(`\nEnviando ${scenarios.length} e-mails de teste...\n`)
|
|
|
|
let success = 0
|
|
let failed = 0
|
|
|
|
for (const scenario of scenarios) {
|
|
try {
|
|
process.stdout.write(` ${scenario.name}... `)
|
|
const html = await scenario.render()
|
|
await sendSmtpMail(smtp, targetEmail, scenario.subject, html)
|
|
console.log("OK")
|
|
success++
|
|
// Pequeno delay entre envios para evitar rate limit
|
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
} catch (error) {
|
|
console.log(`ERRO: ${error instanceof Error ? error.message : error}`)
|
|
failed++
|
|
}
|
|
}
|
|
|
|
console.log("\n" + "=".repeat(60))
|
|
console.log(`Resultado: ${success} enviados, ${failed} falharam`)
|
|
console.log("=".repeat(60))
|
|
|
|
if (failed > 0) {
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error("Erro fatal:", error)
|
|
process.exit(1)
|
|
})
|