From 30f4d4f51ebc15963dab2295703df57c9aab7f6d Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 26 Jul 2026 18:02:07 +0000 Subject: [PATCH] feat(leads): schedule the campaign tick so outreach runs unattended MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The campaign machinery existed but nothing drove it — campaigns only moved when someone clicked "Run now", which makes it manual outreach with extra steps. This is the pg_cron job that closes the loop. Every 15 minutes, matching scan-watches. The tick is cheap and usually idle: it selects only active campaigns and each is bounded per tick (15 discovered, 8 researched, 5 sends). Faster would not help, because the slow step is the scan worker and the daily caps are the real throttle. A tick will not send from a campaign with auto_send off, which is the default. Those discover, scan, research and draft, logging every message as a dry run — turning sending on stays a separate deliberate act, still gated on a CAN-SPAM postal address. Applied to production; verified scheduled and active. Co-Authored-By: Claude Opus 5 (1M context) --- .../20260726180000_outreach_cron.sql | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 supabase/migrations/20260726180000_outreach_cron.sql diff --git a/supabase/migrations/20260726180000_outreach_cron.sql b/supabase/migrations/20260726180000_outreach_cron.sql new file mode 100644 index 00000000..3c506211 --- /dev/null +++ b/supabase/migrations/20260726180000_outreach_cron.sql @@ -0,0 +1,38 @@ +-- Cron: advance every active outreach campaign. +-- +-- Without this the campaign machinery exists but nothing drives it — +-- campaigns only move when someone clicks "Run now". This is the tick that +-- makes lead generation automated rather than manual. +-- +-- Every 15 minutes, matching scan-watches. The tick is cheap and mostly +-- idle: it selects only active campaigns, and each one is bounded per tick +-- (15 discovered, 8 researched, 5 sends). Faster would not help — the slow +-- step is the scan worker, and the daily send caps are the real throttle. +-- +-- What a tick does NOT do: send email from a campaign with auto_send off, +-- which is the default. Such a campaign discovers, scans, researches and +-- drafts, logging each message as a dry run. Turning sending on is a +-- separate, deliberate act in the UI, and it stays blocked until a CAN-SPAM +-- postal address is set. + +do $$ +begin + if exists (select 1 from cron.job where jobname = 'crawlproof-outreach') then + perform cron.unschedule('crawlproof-outreach'); + end if; +end $$; + +select cron.schedule( + 'crawlproof-outreach', + '*/15 * * * *', + $$ + select net.http_post( + url := (select value from public.cron_config where key = 'site_url') || '/api/cron/outreach', + headers := jsonb_build_object( + 'content-type', 'application/json', + 'x-cron-secret', (select value from public.cron_config where key = 'cron_secret') + ), + body := '{}'::jsonb + ); + $$ +);