type CampaignRetryFailureAlert = { jobName: string; runId: string; error: string; runAt: string; tenantId?: string; campaignId?: string; processedCampaigns: number; totalAttempted: number; totalFailed: number; }; function normalizeWebhookEnabled() { const raw = process.env.CAMPAIGN_RETRY_ALERT_WEBHOOK_URL?.trim(); return raw ? raw : ""; } function normalizeBoolean(value: string | undefined) { if (!value) { return true; } return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase()); } export async function notifyCampaignRetryFailure(payload: CampaignRetryFailureAlert) { const webhookUrl = normalizeWebhookEnabled(); if (!webhookUrl) { return; } const shouldNotify = normalizeBoolean(process.env.CAMPAIGN_RETRY_ALERT_ON_FAILURE); if (!shouldNotify) { return; } const message = { event: "campaign_retry_job_failed", ...payload, timestamp: new Date().toISOString() }; try { await fetch(webhookUrl, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(message) }); } catch { // observability channel should not block retries } }