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
This commit is contained in:
Z User committed 2026-10-28 22:50:42 +00:00
1 parent 7a13dad693
commit 970986fef7
6 files changed
+158 -4

No files matched your search

+10 -4
View File
@@ -72,16 +72,22 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
},
})
// TODO (production): send email to company.email with subject
// "Ihre Reseller-Bewerbung wurde genehmigt — {discountPct}% Rabatt"
// containing the discount and a link to the agency portal.
// Send approval email
const { sendAgencyApprovedEmail } = await import('@/lib/email')
if (application.company.email) {
await sendAgencyApprovedEmail({
to: application.company.email,
companyName: application.company.name,
discountPct,
})
}
return NextResponse.json({
ok: true,
status: 'approved',
discountPct,
email: application.company.email,
message: `Genehmigt mit ${discountPct}% Rabatt. E-Mail an ${application.company.email ?? '(keine E-Mail hinterlegt)'} würde versendet.`,
message: `Genehmigt mit ${discountPct}% Rabatt.${application.company.email ? ` E-Mail an ${application.company.email} versendet.` : ' Keine E-Mail hinterlegt.'}`,
})
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
@@ -60,6 +60,16 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
},
})
// Send rejection email
const { sendAgencyRejectedEmail } = await import('@/lib/email')
if (application.company.email) {
await sendAgencyRejectedEmail({
to: application.company.email,
companyName: application.company.name,
reason,
})
}
return NextResponse.json({ ok: true, status: 'rejected', reason })
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'
+12
View File
@@ -304,6 +304,18 @@ export async function POST(req: Request) {
},
})
// Send license key email to the customer
const { sendLicenseKeyEmail } = await import('@/lib/email')
if (company.email && !parentId) {
// Only send for core licenses (not modules)
sendLicenseKeyEmail({
to: company.email,
licenseKey: key,
product,
plan,
}).catch(() => {}) // fire-and-forget, don't block response
}
return NextResponse.json({ license, pricing: pricing.breakdown }, { status: 201 })
} catch (err) {
const msg = err instanceof Error ? err.message : 'internal server error'