mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-18 05:04:12 +00:00
- 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>
131 lines
4 KiB
TypeScript
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
|
|
}
|