- src/lib/email.ts: SMTP client with pre-built templates: - sendAgencyApprovedEmail (reseller approval + discount) - sendAgencyRejectedEmail (reseller rejection + reason) - sendLicenseKeyEmail (license key delivery) - sendTicketCreatedEmail (ticket confirmation) - Agency approve route: sends approval email after DB update - Agency reject route: sends rejection email with reason - License creation route: sends license key email (fire-and-forget) - Graceful degradation: if SMTP not configured, logs warning + continues - nodemailer + @types/nodemailer installed
119 lines
5.2 KiB
TypeScript
119 lines
5.2 KiB
TypeScript
import type { Transporter } from 'nodemailer'
|
|
import nodemailer from 'nodemailer'
|
|
|
|
/**
|
|
* Email Client
|
|
* ------------
|
|
* Sends transactional emails (agency approval, license key delivery, ticket
|
|
* notifications, invoice sent).
|
|
*
|
|
* Config (env):
|
|
* MAIL_SMTP_HOST - e.g. smtp.your-server.de
|
|
* MAIL_SMTP_PORT - 587 (STARTTLS) or 465 (SSL)
|
|
* MAIL_USER - SMTP username
|
|
* MAIL_PASSWORD - SMTP password
|
|
* MAIL_FROM - sender email (e.g. "AOS <noreply@dfmpanel.eu>")
|
|
*/
|
|
|
|
let transporter: Transporter | null = null
|
|
|
|
function getTransporter(): Transporter | null {
|
|
const host = process.env.MAIL_SMTP_HOST
|
|
const port = process.env.MAIL_SMTP_PORT
|
|
const user = process.env.MAIL_USER
|
|
const pass = process.env.MAIL_PASSWORD
|
|
if (!host || !user || !pass) return null
|
|
if (!transporter) {
|
|
transporter = nodemailer.createTransport({
|
|
host,
|
|
port: port ? parseInt(port, 10) : 587,
|
|
secure: parseInt(port ?? '587', 10) === 465,
|
|
auth: { user, pass },
|
|
})
|
|
}
|
|
return transporter
|
|
}
|
|
|
|
export interface EmailInput {
|
|
to: string
|
|
subject: string
|
|
text?: string
|
|
html?: string
|
|
}
|
|
|
|
export async function sendEmail(input: EmailInput): Promise<{ ok: boolean; error?: string }> {
|
|
const t = getTransporter()
|
|
if (!t) {
|
|
console.warn('[mail] SMTP not configured — email would have been sent to:', input.to, '| subject:', input.subject)
|
|
return { ok: false, error: 'SMTP not configured' }
|
|
}
|
|
const from = process.env.MAIL_FROM || 'AOS <noreply@dfmpanel.eu>'
|
|
try {
|
|
await t.sendMail({
|
|
from,
|
|
to: input.to,
|
|
subject: input.subject,
|
|
text: input.text || input.html?.replace(/<[^>]*>/g, ''),
|
|
html: input.html || input.text,
|
|
})
|
|
return { ok: true }
|
|
} catch (err) {
|
|
return { ok: false, error: err instanceof Error ? err.message : 'send failed' }
|
|
}
|
|
}
|
|
|
|
// --- Pre-built email templates ---
|
|
|
|
export async function sendAgencyApprovedEmail(input: {
|
|
to: string
|
|
companyName: string
|
|
discountPct: number
|
|
}): Promise<void> {
|
|
await sendEmail({
|
|
to: input.to,
|
|
subject: `Ihre Reseller-Bewerbung wurde genehmigt — ${input.discountPct}% Rabatt`,
|
|
text: `Guten Tag,\n\nIhre Bewerbung als Reseller wurde genehmigt.\n\nUnternehmen: ${input.companyName}\nIhr Rabatt: ${input.discountPct}% auf alle Lizenzen\n\nSie können jetzt eigene Kunden anlegen und lizenzieren.\n\nBesuchen Sie: https://dfmpanel.eu\n\nMit freundlichen Grüßen\nDFM Solutions`,
|
|
html: `<p>Guten Tag,</p><p>Ihre Bewerbung als Reseller wurde <strong>genehmigt</strong>.</p><p><strong>Unternehmen:</strong> ${input.companyName}<br><strong>Ihr Rabatt:</strong> ${input.discountPct}% auf alle Lizenzen</p><p>Sie können jetzt eigene Kunden anlegen und lizenzieren.</p><p><a href="https://dfmpanel.eu">Zum Portal →</a></p><p>Mit freundlichen Grüßen<br>DFM Solutions</p>`,
|
|
})
|
|
}
|
|
|
|
export async function sendAgencyRejectedEmail(input: {
|
|
to: string
|
|
companyName: string
|
|
reason: string
|
|
}): Promise<void> {
|
|
await sendEmail({
|
|
to: input.to,
|
|
subject: 'Ihre Reseller-Bewerbung',
|
|
text: `Guten Tag,\n\nIhre Bewerbung als Reseller wurde leider abgelehnt.\n\nUnternehmen: ${input.companyName}\nGrund: ${input.reason}\n\nBei Fragen kontaktieren Sie uns unter support@dfmpanel.eu\n\nMit freundlichen Grüßen\nDFM Solutions`,
|
|
html: `<p>Guten Tag,</p><p>Ihre Bewerbung als Reseller wurde leider <strong>abgelehnt</strong>.</p><p><strong>Unternehmen:</strong> ${input.companyName}<br><strong>Grund:</strong> ${input.reason}</p><p>Bei Fragen kontaktieren Sie uns unter support@dfmpanel.eu</p><p>Mit freundlichen Grüßen<br>DFM Solutions</p>`,
|
|
})
|
|
}
|
|
|
|
export async function sendLicenseKeyEmail(input: {
|
|
to: string
|
|
licenseKey: string
|
|
product: string
|
|
plan: string
|
|
}): Promise<void> {
|
|
await sendEmail({
|
|
to: input.to,
|
|
subject: `Ihre Lizenz für ${input.product} (${input.plan.toUpperCase()})`,
|
|
text: `Guten Tag,\n\nIhre Lizenz wurde erstellt.\n\nProdukt: ${input.product}\nPlan: ${input.plan}\nLizenzschlüssel: ${input.licenseKey}\n\nSpeichern Sie den Schlüssel sicher. Sie benötigen ihn zur Aktivierung Ihrer Software.\n\nMit freundlichen Grüßen\nDFM Solutions`,
|
|
html: `<p>Guten Tag,</p><p>Ihre Lizenz wurde erstellt.</p><p><strong>Produkt:</strong> ${input.product}<br><strong>Plan:</strong> ${input.plan}<br><strong>Lizenzschlüssel:</strong> <code style="font-size: 16px; background: #f0f0f0; padding: 8px; border-radius: 4px;">${input.licenseKey}</code></p><p>Speichern Sie den Schlüssel sicher. Sie benötigen ihn zur Aktivierung Ihrer Software.</p><p>Mit freundlichen Grüßen<br>DFM Solutions</p>`,
|
|
})
|
|
}
|
|
|
|
export async function sendTicketCreatedEmail(input: {
|
|
to: string
|
|
ticketNumber: string
|
|
subject: string
|
|
}): Promise<void> {
|
|
await sendEmail({
|
|
to: input.to,
|
|
subject: `Ticket ${input.ticketNumber} wurde erstellt`,
|
|
text: `Ihr Support-Ticket wurde erstellt.\n\nTicket: ${input.ticketNumber}\nBetreff: ${input.subject}\n\nWir werden uns so schnell wie möglich um Ihr Anliegen kümmern.\n\nMit freundlichen Grüßen\nDFM Solutions`,
|
|
html: `<p>Ihr Support-Ticket wurde erstellt.</p><p><strong>Ticket:</strong> ${input.ticketNumber}<br><strong>Betreff:</strong> ${input.subject}</p><p>Wir werden uns so schnell wie möglich um Ihr Anliegen kümmern.</p>`,
|
|
})
|
|
}
|