Workflow architecture: scrape → validate → score → draft → human review → send. Validation runs before scoring on purpose — so the priority channel score-leads.mjs picks is a channel proven real, not just a field that happens to be filled in. Every stage is a real, standalone script (Playwright, DNS, local Llama/Ollama, Gemini, Zoho+Gmail SMTP) — not a manual/chat step. Niche-agnostic by design — dental was just the first test scrape; real estate and Chhattisgarh factory/industry scraping followed the same pipeline unchanged. Which niche gets priority is a profile-fit decision, made per campaign. Actual lead counts/results live on a separate scraping-results dashboard, not here.
_shared/ librarybaserow-client.mjs · zoho-mailer.mjs · pdf-gen.mjs
Proven
Generalized from job-engine's working code. Used by every stage below.
scrape-leads.mjsPlaywright · Google Maps
Proven
Live-tested with a real multi-city run — works for any niche/city/country, not tied to one vertical. Fixed two real bugs along the way: a false-positive Instagram handle when Maps' "website" link was actually an Instagram URL, and a wrong City value when an address had no postal-code segment. Scroll depth fixed to match Google's real 120-result-per-search cap. Captures City/Area/Country/Website/WhatsApp/Facebook/Twitter/LinkedIn/TikTok/Discord/Branding-Language as their own Baserow fields, not buried in Notes. Per-search timing logged to logs/scrape_timing.json — real per-city/per-niche throughput data builds up here over time. First real lead-volume numbers, by niche and country, live on the separate scraping-results dashboard (not this page).
validate-leads.mjsPlaywright (IG/LinkedIn/WhatsApp) + DNS MX lookup + HTTP (website)
Proven
Real, standalone, resumable script — not a manual/chat check, same category of tool as scrape-leads.mjs. Checks every channel a lead actually has before it's trusted for outreach: email domain has a real MX record, website loads, Instagram/LinkedIn profile exists, WhatsApp number is real. Also tests phone-only leads (a number in Notes but no WhatsApp field) directly on wa.me and backfills the WhatsApp field if it's real. 3-lead test passed clean after fixing a machine-level DNS resolver bug; now running live across every lead (5,333 and growing — real estate, dental, Chhattisgarh factories/industries, and Shabiha's Islamic-brand niche as of 22 Aug) with checkpoint/resume so an interruption never re-does work. A separate --backfill-followers mode fetches Instagram/LinkedIn follower counts for channels already confirmed real — a distinct, later pass, not part of the default run (this is why freshly-scraped rows show a validated Instagram handle but no follower number yet — expected, not a bug). Runs before scoring on purpose — see next row.
score-leads.mjsPass 1: rule-based JS · Pass 2: Ollama (local Llama 3.1) primary → Gemini (multi-key) → DeepSeek fallback
Proven
Two independent passes, both live. Pass 1 (rule-based, no AI, free, instant) proven across every lead scraped so far — writes Digital Category, Recommended Offer, Channel Count, Suggested Channel, SMM Candidate, and a templated Behavior Note. Pass 2 (ICP-fit scoring against a real, filled-in ICP_PROFILE.md) has scored 2,929/5,333 — 2,528 pass the ≥60 match threshold and are ready for drafting. (The gap between scored and total is expected — factories/industries and Shabiha's Islamic-brand leads were only just discovered, 21-22 Aug, and haven't been scored yet.) Local Llama 3.1 (Ollama, free, no quota) is the primary scorer; a 7-key Gemini rotation (one API key per team Gmail account) is the fallback when Ollama is off, then DeepSeek as a last resort. 21 Aug: confirmed both keying off a shared Google Cloud project quota — rotating keys does not multiply daily capacity, only smooths per-minute bursts.
draft-outreach.mjsClaude Haiku + Sonnet escalation · locked message-writing playbook · mechanical fact-check
Proven, migrated 29 Aug
29 Aug: migrated off Ollama/Gemini/DeepSeek entirely — Imran's explicit call, same day and same reason as job-engine's match-jobs.mjs/tailor-and-draft.mjs migration. Now runs on Claude Haiku via the Claude Code CLI (Imran's own subscription, no separate API key), Sonnet as a single escalation retry when a response fails validation. Live-tested on real rows across English, Arabic, and Bilingual-branded leads (the exact risk category a 21 Aug test on this same file flagged for encoding corruption) — clean, no corruption, confirmed directly against Baserow. Real fabrication caught and fixed the same day: a test draft claimed "your 5★ rating" and "takes about a week to set up" — neither was in that lead's real Notes. A worded "don't invent numbers" rule alone didn't stop it (same lesson as job-engine's resume drafting), so the mechanical fact-checker was extended to also catch star/review claims and any implementation-timeframe claim, not just percentages — re-tested clean afterward. ~2,176 qualified leads drafted — real per-lead sender (Imran/Gulfisha/Shabiha, matching the locked outreach-routing table by niche+country; "Ada" retired from outreach entirely, 18 Aug). Always drafted in English regardless of the business's own branding script, except Hindi/Bilingual leads which get native-language copy — the prompt itself now carries this instruction directly, no more language-based provider routing. ~178 have no digital channel at all — flagged in Notes for phone/in-person outreach, not skipped.
send-email.mjsZoho + Gmail SMTP, per-mailbox daily-cap discipline
Proven — live, parallel
Credential issue resolved. 21 Aug rebuilt: caps are now PER MAILBOX (35/day each, not one shared 35/day total — the old shared cap silently blocked a real 60-email day's target), and ready rows are grouped by resolved mailbox and sent CONCURRENTLY (up to 2/mailbox/tick) so two mailboxes never starve each other on row order. Real sender routed by niche+country via outreach-routing.mjs (Imran/Gulfisha/Shabiha only — "Ada" retired 18 Aug, never a real prospect-facing sender). 279 real emails sent all-time (151 in a single day, 21 Aug) across 6 mailboxes (ImranZoho, GulfishaZoho, ContactZoho, ImranGmailNetwork, GulfishaGmail01/02). Scheduled task AriseOutreachSendEmail, every 5 min.
ig-cold-dm.mjsPlaywright, saved-session login, warm-up ramp
Not started
Code complete: multi-account rotation, per-account daily cap with warm-up ramp, human-like typing delay, auto-pause + Telegram alert on any restriction language. Needs ig-login-setup.mjs run once per real IG account before first live send.
check-replies.mjsIMAP, 5 mailboxes (4 Zoho + 1 Gmail), email only
Proven — live, scheduled
21 Aug: wired up real bounce detection — Baserow already had Bounced/Bounce Reason columns that nothing was writing to; bounce DSNs are now matched against sent rows by scanning the bounce body for a known recipient address and flagged, instead of being silently skipped. Added ImranGmailNetwork (arisenetwork.cg@gmail.com) to the watch list — real-estate India sends had zero reply/bounce visibility before this. New scheduled task AriseOutreachCheckReplies, every 30 min (previously only job-engine's copy was scheduled). Instagram DM replies still aren't covered (would need the cold-DM accounts wired into growth-engine's Graph webhook, or manual checks).
outreach-dashboardengine.arisegulf.com/outreach-dashboard
Proven
Static HTML, same visual family as job-dashboard. growth-engine's Docker container rebuilt and restarted to serve it — verified live, no regression on existing routes.