diff --git a/app/api/cron/sponsor-outreach/route.ts b/app/api/cron/sponsor-outreach/route.ts index e3806ab3..3d7d4da8 100644 --- a/app/api/cron/sponsor-outreach/route.ts +++ b/app/api/cron/sponsor-outreach/route.ts @@ -5,9 +5,7 @@ import { sanityWriteClient } from '@/lib/sanity-write-client' import { generateOutreachEmail } from '@/lib/sponsor/gemini-outreach' import { sendSponsorEmail } from '@/lib/sponsor/email-service' import type { SponsorPoolEntry } from '@/lib/sponsor/gemini-outreach' - -const MAX_PER_RUN = 5 -const COOLDOWN_DAYS = 14 +import { getConfig } from '@/lib/config' export async function POST(request: Request) { // Auth: Bearer token check against CRON_SECRET @@ -25,9 +23,19 @@ export async function POST(request: Request) { try { console.log('[SPONSOR] Starting outbound sponsor outreach cron...') + // Fetch config from Sanity singleton + const sponsorCfg = await getConfig("sponsor_config"); + const maxPerRun = sponsorCfg.maxOutreachPerRun; + const cooldownDays = sponsorCfg.cooldownDays; + + // Build rate card string from config tiers + const rateCard = sponsorCfg.rateCardTiers + .map((t) => `- ${t.name} ($${t.price}) — ${t.description}`) + .join('\n'); + // Calculate the cutoff date for cooldown const cutoffDate = new Date() - cutoffDate.setDate(cutoffDate.getDate() - COOLDOWN_DAYS) + cutoffDate.setDate(cutoffDate.getDate() - cooldownDays) const cutoffISO = cutoffDate.toISOString() // Query Sanity for eligible sponsor pool entries @@ -38,7 +46,7 @@ export async function POST(request: Request) { !defined(lastContactedAt) || lastContactedAt < $cutoffDate ) - ] | order(relevanceScore desc) [0...${MAX_PER_RUN - 1}] { + ] | order(relevanceScore desc) [0...${maxPerRun - 1}] { _id, companyName, contactName, @@ -67,8 +75,8 @@ export async function POST(request: Request) { for (const sponsor of sponsors) { try { - // Generate personalized outreach email - const email = await generateOutreachEmail(sponsor) + // Generate personalized outreach email with config rate card + const email = await generateOutreachEmail(sponsor, rateCard) // Send the email (stubbed) const sendResult = await sendSponsorEmail( diff --git a/app/api/webhooks/sanity-distribute/route.ts b/app/api/webhooks/sanity-distribute/route.ts index bfe3c7c5..0df1c41c 100644 --- a/app/api/webhooks/sanity-distribute/route.ts +++ b/app/api/webhooks/sanity-distribute/route.ts @@ -5,6 +5,7 @@ import { generateWithGemini } from "@/lib/gemini"; import { uploadVideo, uploadShort, generateShortsMetadata } from "@/lib/youtube-upload"; import { notifySubscribers } from "@/lib/resend-notify"; import { postVideoAnnouncement } from "@/lib/x-social"; +import { getConfig } from "@/lib/config"; const WEBHOOK_SECRET = process.env.SANITY_WEBHOOK_SECRET; @@ -140,6 +141,9 @@ async function appendDistributionLog(docId: string, entries: DistributionLogEntr async function runDistribution(docId: string, doc: AutomatedVideoDoc): Promise { const log: DistributionLogEntry[] = []; + // Fetch distribution config from Sanity singleton + const distConfig = await getConfig("distribution_config"); + try { await updateStatus(docId, "uploading"); @@ -197,7 +201,7 @@ async function runDistribution(docId: string, doc: AutomatedVideoDoc): Promise { const apiKey = process.env.RESEND_API_KEY; if (!apiKey) { @@ -19,8 +21,8 @@ export async function notifySubscribers(opts: { const resend = new Resend(apiKey); await resend.emails.send({ - from: "CodingCat.dev ", - to: ["subscribers@codingcat.dev"], // TODO: fetch subscriber list + from: `CodingCat.dev <${opts.fromEmail || "noreply@codingcat.dev"}>`, + to: opts.notificationEmails || ["subscribers@codingcat.dev"], subject: opts.subject, html: `

${opts.videoTitle}

diff --git a/lib/sponsor/gemini-outreach.ts b/lib/sponsor/gemini-outreach.ts index 056cf7f8..3f9775f2 100644 --- a/lib/sponsor/gemini-outreach.ts +++ b/lib/sponsor/gemini-outreach.ts @@ -1,4 +1,5 @@ import { GoogleGenerativeAI } from '@google/generative-ai' +import { getConfigValue } from '@/lib/config' export interface SponsorPoolEntry { _id: string @@ -16,7 +17,7 @@ export interface OutreachEmail { body: string } -const RATE_CARD = ` +const DEFAULT_RATE_CARD = ` CodingCat.dev Sponsorship Tiers: - Dedicated Video ($4,000) — Full dedicated video about your product - Integrated Mid-Roll Ad ($1,800) — Mid-roll advertisement in our videos @@ -33,7 +34,7 @@ Our audience: 50K+ developers interested in web development, JavaScript/TypeScri */ export async function generateOutreachEmail( sponsor: SponsorPoolEntry, - rateCard: string = RATE_CARD + rateCard: string = DEFAULT_RATE_CARD ): Promise { const apiKey = process.env.GEMINI_API_KEY if (!apiKey) { @@ -42,7 +43,8 @@ export async function generateOutreachEmail( } const genAI = new GoogleGenerativeAI(apiKey) - const model = genAI.getGenerativeModel({ model: process.env.GEMINI_MODEL || 'gemini-2.5-flash' }) + const geminiModel = await getConfigValue("pipeline_config", "geminiModel", "gemini-2.0-flash"); + const model = genAI.getGenerativeModel({ model: geminiModel }) const optOutUrl = sponsor.optOutToken ? `${process.env.NEXT_PUBLIC_URL || 'https://codingcat.dev'}/api/sponsor/opt-out?token=${sponsor.optOutToken}` @@ -76,7 +78,7 @@ Respond ONLY with valid JSON, no markdown formatting: const response = result.response const text = response.text().trim() - const jsonStr = text.replace(/^\`\`\`json?\n?/i, '').replace(/\n?\`\`\`$/i, '').trim() + const jsonStr = text.replace(/^```json?\n?/i, '').replace(/\n?```$/i, '').trim() const parsed = JSON.parse(jsonStr) as OutreachEmail console.log('[SPONSOR] Gemini outreach email generated for:', sponsor.companyName)