import nodemailer from 'nodemailer' const SMTP_HOST = process.env.SMTP_HOST const SMTP_PORT = parseInt(process.env.SMTP_PORT || '587', 10) const SMTP_USER = process.env.SMTP_USER const SMTP_PASS = process.env.SMTP_PASS const STAKEHOLDER_EMAIL = process.env.STAKEHOLDER_EMAIL || 'knie@d-hive.de' const BASE_URL = process.env.BASE_URL || 'https://andreknie.de' let transporter = null export class MailerNotReadyError extends Error { constructor() { super('Mailer ist nicht konfiguriert.') this.name = 'MailerNotReadyError' this.code = 'MAILER_NOT_READY' } } export function isMailerReady() { return Boolean(SMTP_HOST && SMTP_USER && SMTP_PASS) } function getTransporter() { if (!transporter && SMTP_HOST && SMTP_USER && SMTP_PASS) { transporter = nodemailer.createTransport({ host: SMTP_HOST, port: SMTP_PORT, secure: SMTP_PORT === 465, auth: { user: SMTP_USER, pass: SMTP_PASS }, }) } return transporter } export async function sendConfirmationEmail(to, type, token) { const t = getTransporter() if (!t) throw new MailerNotReadyError() const confirmUrl = `${BASE_URL}/api/${type}/confirm/${token}` const subjects = { contact: 'Bitte bestätige deine Kontaktanfrage', 'talk-request': 'Bitte bestätige deine Vortragsanfrage', newsletter: 'Bitte bestätige dein Newsletter-Abo', } await t.sendMail({ from: SMTP_USER, to, subject: subjects[type] || 'Bestätigung erforderlich', text: `Bitte bestätige deine Anfrage:\n\n${confirmUrl}\n\nDieser Link ist 48 Stunden gültig.`, }) } export async function sendStakeholderNotification(type, data) { const t = getTransporter() if (!t) throw new MailerNotReadyError() const subjects = { contact: `Neue Kontaktanfrage von ${data.name}`, 'talk-request': `Neue Vortragsanfrage von ${data.name}`, 'resource-download': `Ressourcenanfrage von ${data.name}`, } const body = [ 'Bitte die angefragten Inhalte prüfen und bei positiver Prüfung ein aktuelles Profil von André verschicken.', '', ...Object.entries(data).map(([key, val]) => `${key}: ${val}`), ].join('\n') await t.sendMail({ from: SMTP_USER, to: type === 'resource-download' ? 'kontakt@d-hive.de' : STAKEHOLDER_EMAIL, subject: subjects[type] || `Neue Anfrage (${type})`, text: body, }) } export { STAKEHOLDER_EMAIL, BASE_URL } /** * Send the speaker CV PDF as an attachment to the requester, and notify the * stakeholder about the lead. If the PDF is missing, the stakeholder is still * notified so the CV can be sent manually. * @returns {{ sent: boolean, pdfMissing: boolean }} */ export async function sendSpeakerCv(to, lead, pdfPath) { const { existsSync } = await import('fs') const t = getTransporter() const pdfMissing = !pdfPath || !existsSync(pdfPath) if (!t) throw new MailerNotReadyError() if (!pdfMissing) { await t.sendMail({ from: SMTP_USER, to, subject: 'Dein Speaker CV von Dr. Andre Knie', text: `Hallo ${lead.name},\n\nvielen Dank für dein Interesse. Im Anhang findest du meinen Speaker CV.\n\nBeste Grüße\nAndre Knie`, attachments: [{ filename: 'Speaker-CV-Andre-Knie.pdf', path: pdfPath }], }) } await notifyStakeholderSpeakerCv(lead, pdfMissing) return { sent: !pdfMissing, pdfMissing } } async function notifyStakeholderSpeakerCv(lead, pdfMissing) { const t = getTransporter() const body = [ `Name: ${lead.name}`, `E-Mail: ${lead.email}`, `Nachricht: ${lead.message}`, `Zeitpunkt: ${lead.timestamp}`, pdfMissing ? 'ACHTUNG: PDF fehlte — CV wurde NICHT automatisch versandt, bitte manuell senden.' : 'CV wurde automatisch an den Anfragenden gesendet.', ].join('\n') if (!t) throw new MailerNotReadyError() await t.sendMail({ from: SMTP_USER, to: STAKEHOLDER_EMAIL, subject: `Speaker-CV-Anfrage von ${lead.name}`, text: body, }) }