diff --git a/app/api/health/route.ts b/app/api/health/route.ts new file mode 100644 index 0000000..ce1d107 --- /dev/null +++ b/app/api/health/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from "next/server"; +import { logLine } from "@/lib/logger"; + +export async function GET() { + logLine("health.hit", { ok: true }); + return NextResponse.json({ ok: true }); +} diff --git a/app/api/signup/route.ts b/app/api/signup/route.ts index 77b0678..af3a9ff 100644 --- a/app/api/signup/route.ts +++ b/app/api/signup/route.ts @@ -5,6 +5,8 @@ import { prisma } from "@/lib/prisma"; import { DEFAULT_ALERTS, DEFAULT_DEFAULTS, DEFAULT_SHIFT } from "@/lib/settings"; import { buildVerifyEmail, sendEmail } from "@/lib/email"; import { getBaseUrl } from "@/lib/appUrl"; +import { logLine } from "@/lib/logger"; + function slugify(input: string) { const trimmed = input.trim().toLowerCase(); @@ -120,10 +122,16 @@ export async function POST(req: Request) { text: emailContent.text, html: emailContent.html, }); - } catch { + } catch (err: any) { emailSent = false; + logLine("signup.verify_email.failed", { + email, + message: err?.message, + code: err?.code, + response: err?.response, + responseCode: err?.responseCode, + }); } - return NextResponse.json({ ok: true, verificationRequired: true, diff --git a/lib/email.ts b/lib/email.ts index 1887acf..5695e87 100644 --- a/lib/email.ts +++ b/lib/email.ts @@ -1,4 +1,6 @@ import nodemailer from "nodemailer"; +import { logLine } from "@/lib/logger"; + type EmailPayload = { to: string; @@ -25,11 +27,23 @@ function getTransporter() { throw new Error("SMTP not configured"); } + const smtpDebug = process.env.SMTP_DEBUG === "true"; + logLine("smtp.config", { + host, + port, + secure, + user, + from: process.env.SMTP_FROM, + smtpDebug, + }); + cachedTransport = nodemailer.createTransport({ host, port, secure, auth: { user, pass }, + logger: smtpDebug, + debug: smtpDebug, }); return cachedTransport; @@ -40,15 +54,55 @@ export async function sendEmail(payload: EmailPayload) { if (!from) { throw new Error("SMTP_FROM not configured"); } - - const transporter = getTransporter(); - return transporter.sendMail({ - from, + logLine("email.send.start", { to: payload.to, subject: payload.subject, - text: payload.text, - html: payload.html, + from, }); + + + const transporter = getTransporter(); + try { + const info = await transporter.sendMail({ + from, + to: payload.to, + subject: payload.subject, + text: payload.text, + html: payload.html, + headers: { + "X-Mailer": "MIS Control Tower", + }, + + replyTo: from, + }); + + // Nodemailer response details: + logLine("email.send.ok", { + to: payload.to, + from, + messageId: info.messageId, + response: info.response, + accepted: info.accepted, + rejected: info.rejected, + pending: (info as any).pending, + }); + + return info; + } catch (err: any) { + logLine("email.send.err", { + to: payload.to, + from, + name: err?.name, + message: err?.message, + code: err?.code, + command: err?.command, + response: err?.response, + responseCode: err?.responseCode, + stack: err?.stack, + }); + throw err; + } + } export function buildVerifyEmail(params: { appName: string; verifyUrl: string }) { diff --git a/lib/logger.ts b/lib/logger.ts new file mode 100644 index 0000000..d24ba43 --- /dev/null +++ b/lib/logger.ts @@ -0,0 +1,20 @@ +import fs from "fs"; +import path from "path"; + +const LOG_PATH = process.env.LOG_FILE || "/tmp/mis-control-tower.log"; + +export function logLine(event: string, data: Record = {}) { + const line = JSON.stringify({ + ts: new Date().toISOString(), + event, + ...data, + }); + console.log(line); + try { + fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true }); + fs.appendFileSync(LOG_PATH, line + "\n", { encoding: "utf8" }); + } catch { + // If file logging fails, we still want something: + console.error("[logLine-failed]", line); + } +}