fix: Rollback auf draft bei Versand-Fehlern, Fehlermeldung updateCampaign
- send/route.ts: try/catch + Rollback auf draft wenn Empfänger-Query oder Queue-Enqueue fehlschlägt - campaigns.ts: Fehlermeldung 'Kampagne nicht im Draft-Status' (zuvor enthielt sie 'nicht gefunden' — verursachte falschen 404 in PATCH) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,23 +17,29 @@ export async function POST(req: NextRequest, { params }: { params: { id: string
|
|||||||
return NextResponse.json({ error: 'Nur Draft-Kampagnen können versendet werden' }, { status: 400 })
|
return NextResponse.json({ error: 'Nur Draft-Kampagnen können versendet werden' }, { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status auf 'sending' setzen — Ergebnis prüfen (Fix C1)
|
|
||||||
const statusResult = await updateCampaignStatus(tenantId, params.id, 'sending')
|
const statusResult = await updateCampaignStatus(tenantId, params.id, 'sending')
|
||||||
if (!statusResult.ok) {
|
if (!statusResult.ok) {
|
||||||
return NextResponse.json({ error: statusResult.error.message }, { status: 500 })
|
return NextResponse.json({ error: statusResult.error.message }, { status: 500 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipients = await withTenant(tenantId, (client) =>
|
let recipients: { email: string }[]
|
||||||
client.query<{ email: string }>(
|
try {
|
||||||
`SELECT s.email FROM subscribers s
|
recipients = await withTenant(tenantId, (client) =>
|
||||||
JOIN campaign_recipients cr ON cr.campaign_id = $1
|
client.query<{ email: string }>(
|
||||||
WHERE (cr.list_id IS NULL OR s.list_id = cr.list_id)
|
`SELECT s.email FROM subscribers s
|
||||||
AND s.status = 'active'`,
|
JOIN campaign_recipients cr ON cr.campaign_id = $1
|
||||||
[params.id]
|
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 (Fix C3)
|
// Suppression-Check PFLICHT — kein Opt-out-Empfänger darf in die Queue
|
||||||
const unsuppressedRecipients = await Promise.all(
|
const unsuppressedRecipients = await Promise.all(
|
||||||
recipients.map(async (r) => {
|
recipients.map(async (r) => {
|
||||||
const suppressed = await checkSuppression(tenantId, r.email)
|
const suppressed = await checkSuppression(tenantId, r.email)
|
||||||
@@ -41,24 +47,30 @@ export async function POST(req: NextRequest, { params }: { params: { id: string
|
|||||||
})
|
})
|
||||||
).then((results) => results.filter((r): r is { email: string } => r !== null))
|
).then((results) => results.filter((r): r is { email: string } => r !== null))
|
||||||
|
|
||||||
// Bulk-Enqueue — schnell, kein sequenzielles await (Fix C2)
|
let addedJobs: { id?: string }[]
|
||||||
const addedJobs = await emailSendQueue.addBulk(
|
try {
|
||||||
unsuppressedRecipients.map((recipient) => ({
|
addedJobs = await emailSendQueue.addBulk(
|
||||||
name: 'send',
|
unsuppressedRecipients.map((recipient) => ({
|
||||||
data: {
|
name: 'send',
|
||||||
tenantId,
|
data: {
|
||||||
campaignId: params.id,
|
tenantId,
|
||||||
recipientEmail: recipient.email,
|
campaignId: params.id,
|
||||||
recipientHash: hashEmail(recipient.email),
|
recipientEmail: recipient.email,
|
||||||
},
|
recipientHash: hashEmail(recipient.email),
|
||||||
opts: {
|
},
|
||||||
attempts: 3,
|
opts: {
|
||||||
backoff: { type: 'exponential' as const, delay: 2000 },
|
attempts: 3,
|
||||||
removeOnComplete: 100,
|
backoff: { type: 'exponential' as const, delay: 2000 },
|
||||||
removeOnFail: { count: 500 },
|
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 })
|
return NextResponse.json({ queued: addedJobs.length })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export async function updateCampaign(
|
|||||||
values
|
values
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (rows.length === 0) return err(new Error('Kampagne nicht gefunden oder nicht im Draft-Status'))
|
if (rows.length === 0) return err(new Error('Kampagne nicht im Draft-Status'))
|
||||||
return ok(rowToCampaign(rows[0]))
|
return ok(rowToCampaign(rows[0]))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return err(e instanceof Error ? e : new Error(String(e)))
|
return err(e instanceof Error ? e : new Error(String(e)))
|
||||||
|
|||||||
Reference in New Issue
Block a user