Files
AOS/src/lib/email.ts
T
Z User 970986fef7 feat: email integration (nodemailer) + wire into agency/license flows
- 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
2026-10-28 22:50:42 +00:00

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>`,
})
}