cms.c2sgmbh/src/hooks/sendFormNotification.ts
Martin Porwoll 19fcb4d837 feat: implement multi-tenant email system with logging
- Add Payload email adapter for system emails (auth, password reset)
- Add EmailLogs collection for tracking all sent emails
- Extend Tenants collection with SMTP configuration fields
- Implement tenant-specific email service with transporter caching
- Add /api/send-email endpoint with:
  - Authentication required
  - Tenant access control (users can only send for their tenants)
  - Rate limiting (10 emails/minute per user)
- Add form submission notification hook with email logging
- Add cache invalidation hook for tenant email config changes

Security:
- SMTP passwords are never returned in API responses
- Passwords are preserved when field is left empty on update
- Only super admins can delete email logs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 20:16:54 +00:00

131 lines
4 KiB
TypeScript

import type { CollectionAfterChangeHook } from 'payload'
import { sendTenantEmail } from '../lib/email/tenant-email-service'
interface SubmissionData {
field: string
value: string
}
interface FormEmail {
emailTo?: string | null
cc?: string | null
bcc?: string | null
replyTo?: string | null
emailFrom?: string | null
subject?: string | null
message?: string | null
}
interface FormDocument {
id: number | string
title: string
emails?: FormEmail[]
tenant?: { id: number | string } | number | string
}
/**
* Hook: Sendet E-Mail-Benachrichtigungen bei neuen Formular-Einsendungen.
* Verwendet den Tenant-spezifischen E-Mail-Service.
*/
export const sendFormNotification: CollectionAfterChangeHook = async ({
doc,
req,
operation,
}) => {
// Nur bei neuen Einsendungen
if (operation !== 'create') return doc
const { payload } = req
try {
// Form laden mit Details
const form = (await payload.findByID({
collection: 'forms',
id: doc.form,
depth: 1,
})) as FormDocument | null
if (!form) {
console.warn('[Forms] Form not found for submission:', doc.form)
return doc
}
// Prüfen ob E-Mail-Benachrichtigungen konfiguriert sind
if (!form.emails || form.emails.length === 0) {
return doc
}
// Tenant ermitteln
const tenantId = typeof form.tenant === 'object' ? form.tenant?.id : form.tenant
if (!tenantId) {
console.warn('[Forms] No tenant found for form submission, skipping email')
return doc
}
// Daten formatieren
const submissionData = doc.submissionData as SubmissionData[] | undefined
const dataHtml = submissionData
? submissionData
.map(
(item) =>
`<tr><td style="padding: 8px; border: 1px solid #ddd;"><strong>${item.field}</strong></td><td style="padding: 8px; border: 1px solid #ddd;">${item.value || '-'}</td></tr>`,
)
.join('')
: '<tr><td colspan="2">Keine Daten</td></tr>'
const dataText = submissionData
? submissionData.map((item) => `${item.field}: ${item.value || '-'}`).join('\n')
: 'Keine Daten'
// E-Mails senden für jede konfigurierte E-Mail-Adresse
for (const emailConfig of form.emails) {
if (!emailConfig.emailTo) continue
const subject = emailConfig.subject || `Neue Formular-Einsendung: ${form.title}`
await sendTenantEmail(payload, tenantId, {
to: emailConfig.emailTo,
subject,
replyTo: emailConfig.replyTo || undefined,
source: 'form',
metadata: {
formId: form.id,
formTitle: form.title,
submissionId: doc.id,
},
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #333;">Neue Einsendung über "${form.title}"</h2>
${emailConfig.message ? `<p>${emailConfig.message}</p>` : ''}
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<thead>
<tr style="background-color: #f5f5f5;">
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Feld</th>
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Wert</th>
</tr>
</thead>
<tbody>
${dataHtml}
</tbody>
</table>
<p style="color: #666; font-size: 12px;">
Gesendet am ${new Date().toLocaleString('de-DE')}
</p>
</div>
`,
text: `Neue Einsendung über "${form.title}"\n\n${emailConfig.message || ''}\n\n${dataText}\n\nGesendet am ${new Date().toLocaleString('de-DE')}`,
})
console.log(`[Forms] Notification sent to ${emailConfig.emailTo} for form ${form.title}`)
}
} catch (error) {
console.error('[Forms] Error sending notification:', error)
// Fehler nicht weiterwerfen, damit die Einsendung trotzdem gespeichert wird
}
return doc
}