mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-18 01:34:11 +00:00
- Fix rate limiter: await formLimiter.check() (was missing await) - Prevent duplicate confirmation emails: add context.skipNewsletterEmail flag - Service sets flag when creating/updating subscribers via API - Hook skips email sending when flag is present - Admin panel creations still trigger the hook - Fix unsubscribe links: use subscriber ID instead of token for welcome/unsubscribe emails - Token is nullified after confirmation, making old links invalid - ID-based lookups always work 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
// src/hooks/sendNewsletterConfirmation.ts
|
|
|
|
import type { CollectionAfterChangeHook } from 'payload'
|
|
import type { NewsletterSubscriber, Tenant } from '../payload-types'
|
|
import { sendTenantEmail } from '../lib/email/tenant-email-service'
|
|
import {
|
|
getConfirmationEmailHtml,
|
|
getConfirmationEmailText,
|
|
type NewsletterTemplateData,
|
|
} from '../lib/email/newsletter-templates'
|
|
|
|
/**
|
|
* Hook: Sendet automatisch Double Opt-In E-Mail bei neuen Newsletter-Anmeldungen
|
|
*
|
|
* Wird nur bei neuen Subscribern mit Status "pending" ausgeführt.
|
|
* Vermeidet doppelten Versand durch Prüfung ob E-Mail bereits über API/Service gesendet wurde.
|
|
*
|
|
* Der Hook wird NICHT ausgeführt wenn:
|
|
* - Die Erstellung via overrideAccess erfolgt (Newsletter-Service nutzt dies)
|
|
* - Ein Kontext-Flag gesetzt ist
|
|
*/
|
|
export const sendNewsletterConfirmation: CollectionAfterChangeHook<NewsletterSubscriber> = async ({
|
|
doc,
|
|
operation,
|
|
req,
|
|
previousDoc,
|
|
context,
|
|
}) => {
|
|
// Skip wenn via Newsletter-Service erstellt (verwendet overrideAccess)
|
|
// Der Service sendet die E-Mail selbst
|
|
if (context?.skipNewsletterEmail) {
|
|
return doc
|
|
}
|
|
|
|
// Nur bei neuen Anmeldungen (create) oder wenn Status auf "pending" geändert wird
|
|
const isNew = operation === 'create'
|
|
const isResubscribe =
|
|
operation === 'update' &&
|
|
previousDoc?.status === 'unsubscribed' &&
|
|
doc.status === 'pending'
|
|
|
|
if (!isNew && !isResubscribe) {
|
|
return doc
|
|
}
|
|
|
|
// Nur bei Status "pending" (Double Opt-In ausstehend)
|
|
if (doc.status !== 'pending') {
|
|
return doc
|
|
}
|
|
|
|
// Prüfen ob Token vorhanden
|
|
if (!doc.confirmationToken) {
|
|
console.warn('[Newsletter] No confirmation token found for subscriber:', doc.id)
|
|
return doc
|
|
}
|
|
|
|
// Tenant-ID ermitteln
|
|
const tenantId = typeof doc.tenant === 'object' ? doc.tenant?.id : doc.tenant
|
|
|
|
if (!tenantId) {
|
|
console.warn('[Newsletter] No tenant found for subscriber:', doc.id)
|
|
return doc
|
|
}
|
|
|
|
try {
|
|
// Tenant laden
|
|
const tenant = (await req.payload.findByID({
|
|
collection: 'tenants',
|
|
id: tenantId,
|
|
depth: 0,
|
|
})) as Tenant
|
|
|
|
if (!tenant) {
|
|
console.error('[Newsletter] Tenant not found:', tenantId)
|
|
return doc
|
|
}
|
|
|
|
// Template-Daten zusammenstellen
|
|
const baseUrl = process.env.PAYLOAD_PUBLIC_SERVER_URL || 'https://pl.c2sgmbh.de'
|
|
const tenantWebsite = tenant.domains?.[0]?.domain
|
|
? `https://${tenant.domains[0].domain}`
|
|
: undefined
|
|
|
|
const templateData: NewsletterTemplateData = {
|
|
firstName: doc.firstName || undefined,
|
|
email: doc.email,
|
|
confirmationUrl: `${baseUrl}/api/newsletter/confirm?token=${doc.confirmationToken}`,
|
|
// Für Confirmation-E-Mail Token verwenden (noch gültig), aber ID als Fallback
|
|
unsubscribeUrl: `${baseUrl}/api/newsletter/unsubscribe?token=${doc.confirmationToken || doc.id}`,
|
|
tenantName: tenant.name,
|
|
tenantWebsite,
|
|
privacyPolicyUrl: tenantWebsite ? `${tenantWebsite}/datenschutz` : undefined,
|
|
}
|
|
|
|
// Double Opt-In E-Mail senden
|
|
const result = await sendTenantEmail(req.payload, tenantId, {
|
|
to: doc.email,
|
|
subject: `Newsletter-Anmeldung bestätigen - ${tenant.name}`,
|
|
html: getConfirmationEmailHtml(templateData),
|
|
text: getConfirmationEmailText(templateData),
|
|
source: 'newsletter',
|
|
metadata: {
|
|
type: 'double-opt-in',
|
|
subscriberId: doc.id,
|
|
triggeredBy: 'hook',
|
|
},
|
|
})
|
|
|
|
if (result.success) {
|
|
console.log(`[Newsletter] Confirmation email sent to ${doc.email}`)
|
|
} else {
|
|
console.error(`[Newsletter] Failed to send confirmation email to ${doc.email}:`, result.error)
|
|
}
|
|
} catch (error) {
|
|
console.error('[Newsletter] Error sending confirmation email:', error)
|
|
}
|
|
|
|
return doc
|
|
}
|