- Client: API-Basis-URL (joinApiUrl, /v1-Falle), Vite strictPort + Proxy 127.0.0.1, Nicht-JSON-Fehler - Server: /api-404 ohne Wildcard-Bug, SPA-Fallback, Auth-Middleware, Cron, Mailer, Crypto - Routen: OAuth-State, Email/Stripe/Analytics; client/.env.example Made-with: Cursor
69 lines
1.8 KiB
JavaScript
69 lines
1.8 KiB
JavaScript
/**
|
|
* Plain SMTP mailer (nodemailer). Optional: if SMTP not configured, send is a no-op.
|
|
*/
|
|
|
|
import nodemailer from 'nodemailer'
|
|
import { readFileSync } from 'fs'
|
|
import { join, dirname } from 'path'
|
|
import { fileURLToPath } from 'url'
|
|
import { log } from '../middleware/logger.mjs'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
|
let transporter = null
|
|
|
|
function getTransporter() {
|
|
const host = process.env.SMTP_HOST
|
|
const user = process.env.SMTP_USER
|
|
const pass = process.env.SMTP_PASS
|
|
if (!host || !user || !pass) {
|
|
return null
|
|
}
|
|
if (!transporter) {
|
|
transporter = nodemailer.createTransport({
|
|
host,
|
|
port: parseInt(process.env.SMTP_PORT || '587', 10),
|
|
secure: process.env.SMTP_SECURE === 'true',
|
|
auth: { user, pass },
|
|
})
|
|
}
|
|
return transporter
|
|
}
|
|
|
|
export function renderTemplate(text, vars) {
|
|
let out = text
|
|
for (const [k, v] of Object.entries(vars || {})) {
|
|
out = out.split(`{{${k}}}`).join(v != null ? String(v) : '')
|
|
}
|
|
return out
|
|
}
|
|
|
|
export function loadEmailTemplate(name) {
|
|
const path = join(__dirname, '..', 'emails', `${name}.txt`)
|
|
return readFileSync(path, 'utf8')
|
|
}
|
|
|
|
/**
|
|
* Send plain-text email. Returns false if SMTP not configured or send failed (logged).
|
|
*/
|
|
export async function sendPlainEmail({ to, subject, text }) {
|
|
const from = process.env.SMTP_FROM || process.env.SMTP_USER
|
|
if (!to || !subject || !text) {
|
|
log.warn('sendPlainEmail: missing to/subject/text')
|
|
return false
|
|
}
|
|
const tx = getTransporter()
|
|
if (!tx) {
|
|
log.warn('SMTP not configured (SMTP_HOST/SMTP_USER/SMTP_PASS); email skipped')
|
|
return false
|
|
}
|
|
try {
|
|
await tx.sendMail({ from, to, subject, text })
|
|
log.info(`Email sent to ${to}: ${subject}`)
|
|
return true
|
|
} catch (e) {
|
|
log.error('sendPlainEmail failed', { error: e.message, to })
|
|
return false
|
|
}
|
|
}
|