const { SMTPServer } = require("smtp-server"); const { simpleParser } = require("mailparser"); const express = require("express"); const SMTP_PORT = parseInt(process.env.SMTP_PORT || "25", 10); const API_PORT = parseInt(process.env.API_PORT || "3025", 10); const API_KEY = process.env.API_KEY || ""; const MAX_AGE_MS = 3600_000; // 1 saat const store = new Map(); function extractCode(text) { if (!text) return null; const m = text.match(/\b(\d{4,8})\b/); return m ? m[1] : null; } function cleanup() { const now = Date.now(); for (const [addr, mails] of store) { const fresh = mails.filter((m) => now - m.ts < MAX_AGE_MS); if (fresh.length) store.set(addr, fresh); else store.delete(addr); } } setInterval(cleanup, 60_000); const smtp = new SMTPServer({ secure: false, authOptional: true, disabledCommands: ["STARTTLS"], onData(stream, session, cb) { const chunks = []; stream.on("data", (c) => chunks.push(c)); stream.on("end", async () => { try { const raw = Buffer.concat(chunks); const parsed = await simpleParser(raw); const recipients = session.envelope.rcptTo.map((r) => r.address.toLowerCase() ); const entry = { ts: Date.now(), from: parsed.from?.text || "", subject: parsed.subject || "", text: parsed.text || "", html: parsed.html || "", code: extractCode(parsed.text) || extractCode(parsed.subject), }; for (const addr of recipients) { if (!store.has(addr)) store.set(addr, []); store.get(addr).push(entry); if (store.get(addr).length > 50) store.get(addr).shift(); } console.log( `[MAIL] ${entry.from} -> ${recipients.join(",")} subj="${entry.subject}" code=${entry.code || "-"}` ); } catch (e) { console.error("[MAIL] parse error:", e.message); } cb(); }); }, }); const app = express(); app.use((req, res, next) => { if (API_KEY && req.headers["x-api-key"] !== API_KEY) { return res.status(401).json({ error: "unauthorized" }); } next(); }); app.get("/health", (_req, res) => { res.json({ status: "ok", stored: store.size }); }); app.get("/mail/:address", (req, res) => { const addr = req.params.address.toLowerCase(); const mails = store.get(addr) || []; const limit = parseInt(req.query.limit || "10", 10); res.json({ address: addr, count: mails.length, mails: mails.slice(-limit) }); }); app.get("/code/:address", (req, res) => { const addr = req.params.address.toLowerCase(); const mails = store.get(addr) || []; const last = [...mails].reverse().find((m) => m.code); if (last) { res.json({ address: addr, code: last.code, subject: last.subject, age_s: Math.round((Date.now() - last.ts) / 1000) }); } else { res.json({ address: addr, code: null, message: "no code found" }); } }); app.get("/codes", (_req, res) => { const result = {}; for (const [addr, mails] of store) { const last = [...mails].reverse().find((m) => m.code); if (last) result[addr] = { code: last.code, age_s: Math.round((Date.now() - last.ts) / 1000) }; } res.json(result); }); app.delete("/mail/:address", (req, res) => { store.delete(req.params.address.toLowerCase()); res.json({ ok: true }); }); smtp.listen(SMTP_PORT, () => console.log(`[SMTP] listening on :${SMTP_PORT}`)); app.listen(API_PORT, () => console.log(`[API] listening on :${API_PORT}`)); smtp.on("error", (e) => console.error("[SMTP] error:", e.message));