import { NextRequest, NextResponse } from 'next/server' import { getCampaign, updateCampaignStatus, getCampaignRecipients } from '../../../../../server/db/campaigns' import { emailSendQueue } from '../../../../../queues/email-send.queue' import { hashEmail } from '../../../../../lib/crypto' import { checkSuppression } from '../../../../../server/suppression/check' import { getTenantId } from '../../../../../lib/tenant-header' export async function POST(req: NextRequest, { params }: { params: { id: string } }) { let tenantId: string try { tenantId = getTenantId(req) } catch { return NextResponse.json({ error: 'Tenant nicht identifizierbar' }, { status: 401 }) } const campaignResult = await getCampaign(tenantId, params.id) if (!campaignResult.ok) { return NextResponse.json({ error: campaignResult.error.message }, { status: 404 }) } if (campaignResult.data.status !== 'draft') { return NextResponse.json({ error: 'Nur Draft-Kampagnen können versendet werden' }, { status: 400 }) } const statusResult = await updateCampaignStatus(tenantId, params.id, 'sending') if (!statusResult.ok) { return NextResponse.json({ error: statusResult.error.message }, { status: 500 }) } const recipientsResult = await getCampaignRecipients(tenantId, params.id) if (!recipientsResult.ok) { // Rollback auf draft — verhindert verwaisten 'sending'-Status await updateCampaignStatus(tenantId, params.id, 'draft') return NextResponse.json({ error: 'Empfänger konnten nicht geladen werden' }, { status: 500 }) } const recipients = recipientsResult.data // Suppression-Check PFLICHT — kein Opt-out-Empfänger darf in die Queue const unsuppressedRecipients = await Promise.all( recipients.map(async (r) => { const suppressed = await checkSuppression(tenantId, r.email) return suppressed ? null : r }) ).then((results) => results.filter((r): r is { email: string } => r !== null)) let addedJobs: { id?: string }[] try { addedJobs = await emailSendQueue.addBulk( unsuppressedRecipients.map((recipient) => ({ name: 'send', data: { tenantId, campaignId: params.id, recipientEmail: recipient.email, recipientHash: hashEmail(recipient.email), }, opts: { attempts: 3, backoff: { type: 'exponential' as const, delay: 2000 }, removeOnComplete: 100, removeOnFail: { count: 500 }, }, })) ) } catch (e) { // Rollback auf draft — Queue-Fehler darf nicht zu verwaisten Kampagnen führen await updateCampaignStatus(tenantId, params.id, 'draft') return NextResponse.json({ error: 'Jobs konnten nicht eingestellt werden' }, { status: 500 }) } return NextResponse.json({ queued: addedJobs.length }) }