import type { APIRoute } from 'astro'; import { absoluteUrl, getEmailBinding, getNewsletterDb, getRuntimeEnv, readEnv, sendCloudflareEmail, siteOrigin, type NewsletterSubscriber, } from '../../../../lib/newsletter'; export const prerender = false; type SendBody = { subject?: string; text?: string; html?: string; dryRun?: boolean; }; function unauthorized(): Response { return new Response('Unauthorized', { status: 401 }); } function authorize(request: Request, runtimeEnv: ReturnType): boolean { const expected = readEnv('NEWSLETTER_ADMIN_TOKEN', runtimeEnv); if (!expected) return false; const header = request.headers.get('authorization') || ''; const bearer = header.toLowerCase().startsWith('bearer ') ? header.slice(7).trim() : ''; return bearer === expected; } export const POST: APIRoute = async ({ request, locals, url }) => { const runtimeEnv = getRuntimeEnv(locals); if (!authorize(request, runtimeEnv)) return unauthorized(); const mode = (readEnv('NEWSLETTER_MODE', runtimeEnv) || readEnv('CONTACT_FORM_MODE', runtimeEnv) || 'dev').toLowerCase(); let body: SendBody; try { body = (await request.json()) as SendBody; } catch { return new Response('Invalid JSON body.', { status: 400 }); } const subject = (body.subject || '').trim(); const text = (body.text || '').trim(); const html = (body.html || '').trim(); if (!subject || !text) { return new Response('Fields subject and text are required.', { status: 400 }); } if (mode !== 'live') { console.info('[newsletter:dev] Campaign send requested', { subject, dryRun: Boolean(body.dryRun), textPreview: text.slice(0, 120), }); return Response.json({ mode, accepted: true, sent: 0, failed: 0, note: 'Dev mode: campaign not sent.', }); } const db = getNewsletterDb(runtimeEnv); if (!db) { return new Response('Newsletter database binding NEWSLETTER_DB is not configured.', { status: 500, }); } const emailBinding = getEmailBinding(runtimeEnv); const fromEmail = readEnv('NEWSLETTER_FROM_EMAIL', runtimeEnv) || readEnv('CONTACT_FORM_FROM_EMAIL', runtimeEnv); if (!emailBinding || !fromEmail) { return new Response('Missing EMAIL binding or from email for newsletter send.', { status: 500, }); } const subscribersResult = await db .prepare( `SELECT * FROM newsletter_subscribers WHERE status = 'active' ORDER BY id ASC`, ) .all(); const subscribers = subscribersResult.results ?? []; const campaignInsert = await db .prepare( `INSERT INTO newsletter_campaigns (subject, body_text, body_html, status, created_by) VALUES (?, ?, ?, ?, ?)`, ) .bind(subject, text, html || null, body.dryRun ? 'draft' : 'sending', 'admin-api') .run(); const campaignId = Number(campaignInsert.meta.last_row_id); const origin = siteOrigin(url, runtimeEnv); if (body.dryRun) { return Response.json({ dryRun: true, campaignId, recipients: subscribers.length, sample: subscribers.slice(0, 5).map((s) => s.email), }); } let sent = 0; let failed = 0; for (const subscriber of subscribers) { const unsubUrl = absoluteUrl( origin, `/api/newsletter/unsubscribe?token=${subscriber.unsubscribe_token}`, ); const personalizedText = `${text}\n\n---\nDisiscrizione: ${unsubUrl}\n`; const personalizedHtml = html ? `${html}

Disiscrizione: ${unsubUrl}

` : undefined; try { await sendCloudflareEmail(emailBinding, { fromEmail, toEmail: subscriber.email, subject, text: personalizedText, html: personalizedHtml, }); await db .prepare( `INSERT INTO newsletter_campaign_sends (campaign_id, subscriber_id, email, status, sent_at) VALUES (?, ?, ?, 'sent', datetime('now'))`, ) .bind(campaignId, subscriber.id, subscriber.email) .run(); sent += 1; } catch (error) { failed += 1; const message = error instanceof Error ? error.message : String(error); await db .prepare( `INSERT INTO newsletter_campaign_sends (campaign_id, subscriber_id, email, status, error, sent_at) VALUES (?, ?, ?, 'failed', ?, datetime('now'))`, ) .bind(campaignId, subscriber.id, subscriber.email, message.slice(0, 500)) .run(); } } await db .prepare( `UPDATE newsletter_campaigns SET status = ?, sent_at = datetime('now') WHERE id = ?`, ) .bind(failed > 0 && sent === 0 ? 'failed' : 'sent', campaignId) .run(); return Response.json({ campaignId, recipients: subscribers.length, sent, failed, }); };