Files
vendoo/lib/mailer.mjs
T
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

215 lines
8.0 KiB
JavaScript

import nodemailer from 'nodemailer';
let transporter = null;
export function initMailer() {
const host = process.env.SMTP_HOST;
const port = parseInt(process.env.SMTP_PORT || '587');
const user = process.env.SMTP_USER;
const pass = process.env.SMTP_PASS;
const from = process.env.SMTP_FROM || user;
if (!host || !user || !pass) {
console.warn('SMTP nicht konfiguriert — Magic Links werden in der Konsole ausgegeben');
return false;
}
transporter = nodemailer.createTransport({
host,
port,
secure: port === 465,
auth: { user, pass },
tls: { rejectUnauthorized: !/^(0|false|no)$/i.test(String(process.env.SMTP_TLS_REJECT_UNAUTHORIZED || 'true')) },
});
return true;
}
function brandedHtml(title, content, buttonText, buttonUrl) {
return `<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width"></head>
<body style="margin:0;padding:0;background:#f5f5f0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f5f5f0;padding:40px 20px">
<tr><td align="center">
<table width="520" cellpadding="0" cellspacing="0" style="background:white;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,0.08);overflow:hidden">
<tr><td style="background:#4a7c59;padding:28px 32px">
<h1 style="margin:0;color:white;font-size:22px;font-weight:700;letter-spacing:-0.5px">Vendoo</h1>
</td></tr>
<tr><td style="padding:32px">
<h2 style="margin:0 0 16px;color:#1a1a1a;font-size:18px">${title}</h2>
<div style="color:#4a4a4a;font-size:14px;line-height:1.7">${content}</div>
${buttonUrl ? `
<div style="margin:28px 0;text-align:center">
<a href="${buttonUrl}" style="display:inline-block;background:#4a7c59;color:white;text-decoration:none;padding:14px 36px;border-radius:8px;font-weight:600;font-size:14px;letter-spacing:0.3px">${buttonText}</a>
</div>
<p style="color:#999;font-size:11px;margin-top:20px">Oder kopiere diesen Link:<br>
<a href="${buttonUrl}" style="color:#4a7c59;word-break:break-all;font-size:11px">${buttonUrl}</a></p>` : ''}
</td></tr>
<tr><td style="padding:20px 32px;background:#fafaf8;border-top:1px solid #eee">
<p style="margin:0;color:#999;font-size:11px;text-align:center">Vendoo — Marketplace Listing Generator</p>
</td></tr>
</table>
</td></tr>
</table>
</body></html>`;
}
export async function sendMagicLink(email, token, baseUrl, type = 'login') {
const url = `${baseUrl}/auth/magic?token=${token}`;
const isInvite = type === 'invite';
const title = isInvite ? 'Du wurdest eingeladen!' : 'Dein Login-Link';
const content = isInvite
? `<p>Du wurdest zum Vendoo eingeladen. Klicke auf den Button, um dein Konto zu aktivieren und loszulegen.</p><p>Der Link ist <strong>48 Stunden</strong> gültig.</p>`
: `<p>Klicke auf den Button, um dich einzuloggen. Kein Passwort nötig.</p><p>Der Link ist <strong>15 Minuten</strong> gültig und kann nur einmal verwendet werden.</p>`;
const buttonText = isInvite ? 'Einladung annehmen' : 'Jetzt einloggen';
const html = brandedHtml(title, content, buttonText, url);
const subject = isInvite ? 'Vendoo — Du wurdest eingeladen' : 'Vendoo — Dein Login-Link';
if (!transporter) {
console.log(`\n${'='.repeat(60)}`);
console.log(`MAGIC LINK (${type}) für ${email}:`);
console.log(url);
console.log(`${'='.repeat(60)}\n`);
return { sent: false, consoleFallback: true, url };
}
try {
await transporter.sendMail({
from: process.env.SMTP_FROM || process.env.SMTP_USER,
to: email,
subject,
html,
});
return { sent: true };
} catch (err) {
console.error('Email senden fehlgeschlagen:', err.message);
console.log(`Fallback — Magic Link für ${email}: ${url}`);
return { sent: false, error: err.message, url };
}
}
export async function sendCredentials(email, password, name, baseUrl) {
const title = 'Dein Account wurde erstellt';
const content = `<p>Hallo${name ? ' ' + name : ''},</p>
<p>Ein Admin hat ein Konto für dich erstellt. Hier sind deine Zugangsdaten:</p>
<table style="margin:16px 0;border-collapse:collapse">
<tr><td style="padding:6px 14px;font-weight:600;color:#666;border-bottom:1px solid #eee">E-Mail</td><td style="padding:6px 14px;border-bottom:1px solid #eee">${email}</td></tr>
<tr><td style="padding:6px 14px;font-weight:600;color:#666;border-bottom:1px solid #eee">Passwort</td><td style="padding:6px 14px;border-bottom:1px solid #eee;font-family:monospace;font-size:15px;letter-spacing:1px">${password}</td></tr>
</table>
<p>Bitte ändere dein Passwort nach dem ersten Login.</p>`;
const buttonText = 'Jetzt einloggen';
const buttonUrl = baseUrl + '/login.html';
const html = brandedHtml(title, content, buttonText, buttonUrl);
const subject2 = 'Vendoo — Dein Account wurde erstellt';
if (!transporter) {
console.log(`\n${'='.repeat(60)}`);
console.log(`CREDENTIALS für ${email}:`);
console.log(`Passwort: ${password}`);
console.log(`Login: ${baseUrl}/login.html`);
console.log(`${'='.repeat(60)}\n`);
return { sent: false, consoleFallback: true };
}
try {
await transporter.sendMail({
from: process.env.SMTP_FROM || process.env.SMTP_USER,
to: email,
subject: subject2,
html,
});
return { sent: true };
} catch (err) {
console.error('Credentials email failed:', err.message);
console.log(`Fallback — Credentials für ${email}: Passwort=${password}`);
return { sent: false, error: err.message };
}
}
export async function sendNotificationEmail(email, subject, title, content) {
const html = brandedHtml(title, content, null, null);
if (!transporter) {
console.log(`Notification for ${email}: ${subject}`);
return { sent: false };
}
try {
await transporter.sendMail({
from: process.env.SMTP_FROM || process.env.SMTP_USER,
to: email,
subject: `Vendoo — ${subject}`,
html,
});
return { sent: true };
} catch (err) {
console.error('Notification email failed:', err.message);
return { sent: false, error: err.message };
}
}
export function isMailerConfigured() {
return transporter !== null;
}
export function reinitMailer(config) {
if (!config || !config.host || !config.user || !config.pass) {
transporter = null;
return false;
}
const port = parseInt(config.port || '587');
transporter = nodemailer.createTransport({
host: config.host,
port,
secure: port === 465,
auth: { user: config.user, pass: config.pass },
tls: { rejectUnauthorized: !/^(0|false|no)$/i.test(String(process.env.SMTP_TLS_REJECT_UNAUTHORIZED || 'true')) },
});
return true;
}
export function getMailerConfig() {
if (!transporter || !transporter.options) {
return {
host: process.env.SMTP_HOST || '',
port: process.env.SMTP_PORT || '587',
user: process.env.SMTP_USER || '',
pass: process.env.SMTP_PASS ? '••••••••' : '',
from: process.env.SMTP_FROM || process.env.SMTP_USER || '',
configured: transporter !== null,
};
}
const opts = transporter.options;
return {
host: opts.host || '',
port: String(opts.port || '587'),
user: opts.auth?.user || '',
pass: opts.auth?.pass ? '••••••••' : '',
from: process.env.SMTP_FROM || opts.auth?.user || '',
configured: true,
};
}
export async function testMailer(toEmail) {
if (!transporter) throw new Error('SMTP nicht konfiguriert');
const html = `<!DOCTYPE html>
<html><head><meta charset="utf-8"></head>
<body style="font-family:system-ui,sans-serif;padding:20px">
<h2 style="color:#4a7c59">Vendoo — SMTP Test</h2>
<p>Diese E-Mail bestätigt, dass deine SMTP-Konfiguration korrekt funktioniert.</p>
<p style="color:#999;font-size:12px">Gesendet: ${new Date().toLocaleString('de-DE')}</p>
</body></html>`;
await transporter.sendMail({
from: process.env.SMTP_FROM || transporter.options?.auth?.user,
to: toEmail,
subject: 'Vendoo — SMTP Testmail',
html,
});
return { sent: true };
}