import { NextRequest, NextResponse } from 'next/server' import { getCampaign, updateCampaignStatus } from '../../../../../server/db/campaigns' import { emailSendQueue } from '../../../../../queues/email-send.queue' import { withTenant } from '../../../../../server/db/tenant' 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 } }) { const tenantId = getTenantId(req) 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 }) } let recipients: { email: string }[] try { recipients = await withTenant(tenantId, (client) => client.query<{ email: string }>( `SELECT s.email FROM subscribers s JOIN campaign_recipients cr ON cr.campaign_id = $1 WHERE (cr.list_id IS NULL OR s.list_id = cr.list_id) AND s.status = 'active'`, [params.id] ) ) } catch (e) { // 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 }) } // 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 }) }