diff --git a/src/app/api/newsletter/subscribe/route.ts b/src/app/api/newsletter/subscribe/route.ts index 9dd4c6a..e36ea69 100644 --- a/src/app/api/newsletter/subscribe/route.ts +++ b/src/app/api/newsletter/subscribe/route.ts @@ -1,76 +1,123 @@ -import { createClient } from '@/lib/supabase/server'; +import { createAdminClient } from '@/lib/supabase/admin'; import { NextRequest, NextResponse } from 'next/server'; import nodemailer from 'nodemailer'; +export const runtime = 'nodejs'; + +// Public newsletter subscribe endpoint. Used by: +// - NewsletterModal (site-wide popup) +// - Header subscribe widget +// - Homepage NewsletterSignup section +// - Footer signup +// +// Why service-role (not the session client): +// bb.newsletter_subscribers' RLS only has an INSERT policy for anon +// ('public_subscribe_newsletter') — upsert needs both INSERT+UPDATE +// to handle re-subscribers. Service-role bypasses RLS cleanly. + export async function POST(req: NextRequest) { try { - const { email, topics } = await req.json(); + const { email, topics, source } = await req.json(); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { - return NextResponse.json({ error: 'Valid email is required' }, { status: 400 }); + return NextResponse.json({ error: 'Please enter a valid email address.' }, { status: 400 }); } - const supabase = await createClient(); + const normalized = email.toLowerCase().trim(); + const supabase = createAdminClient(); // Upsert subscriber (re-subscribe if previously unsubscribed) const { error } = await supabase .from('newsletter_subscribers') .upsert( { - email: email.toLowerCase().trim(), - topics: topics || [], + email: normalized, + topics: topics && Array.isArray(topics) ? topics : [], status: 'active', subscribed_at: new Date().toISOString(), unsubscribed_at: null, }, - { onConflict: 'email' } + { onConflict: 'email' }, ); if (error) { - console.error('Supabase error:', error); - return NextResponse.json({ error: 'Failed to save subscription' }, { status: 500 }); + console.error('newsletter_subscribers upsert failed:', error.message, error.details); + return NextResponse.json( + { error: 'Could not save subscription. Please try again in a moment.' }, + { status: 500 }, + ); } - // Send confirmation email via SMTP - const smtpHost = process.env.SMTP_HOST; - const smtpPort = parseInt(process.env.SMTP_PORT || '587'); - const smtpUser = process.env.SMTP_USER; - const smtpPass = process.env.SMTP_PASS; - const fromEmail = process.env.SMTP_FROM_EMAIL || smtpUser; - const fromName = process.env.SMTP_FROM_NAME || 'Broadcast Beat'; + // Best-effort welcome email — never block the API response on SMTP. + // The signup is already saved to the DB, so we return 200 either way + // and let the email fire in the background. + void sendWelcome(normalized, topics).catch((e) => + console.error('newsletter welcome email failed:', e?.message || e), + ); - if (smtpHost && smtpUser && smtpPass) { - const transporter = nodemailer.createTransport({ - host: smtpHost, - port: smtpPort, - secure: smtpPort === 465, - auth: { user: smtpUser, pass: smtpPass }, - }); - - const topicsList = topics && topics.length > 0 - ? `
You'll receive updates on: ${topics.join(', ')}
` - : ''; - - await transporter.sendMail({ - from: `"${fromName}" <${fromEmail}>`, - to: email, - subject: `Welcome to Broadcast Beat — You're subscribed!`, - html: ` -Thanks for subscribing to Broadcast Beat — the digital platform for broadcast engineering.
- ${topicsList} -You'll be the first to know about the latest news, product launches, and industry insights.
-You're receiving this because you signed up at broadcastbeat.com. Unsubscribe
-You'll receive coverage on: ${topics.join(', ')}
` + : ''; + + await transporter.sendMail({ + from: `"${fromName}" <${fromEmail}>`, + to: email, + subject: 'Welcome to Broadcast Beat', + html: ` + +| + + |