I'm going to show you the exact architecture we use to run a full outbound sales operation — lead sourcing, enrichment, email sequences, CRM, analytics, and follow-up automation — for under $50/month.
No Salesforce. No HubSpot. No Clay. No Instantly.ai.
Just: n8n + PostgreSQL + Redis + SQLite + Resend + Cloudflare Tunnel, all running on a $10.59/month Hetzner VPS.
The Architecture
[Lead Sources]
Apollo.io API → n8n Lead Pipeline
Google Maps (via n8n workflow) → Raw leads
[Processing Layer]
n8n (VPS) → validates, dedupes, enriches
PostgreSQL → raw lead storage
Redis → rate limiting, dedup, cooldowns
[Orchestration Layer]
Claude claude-sonnet-4-6 (local, Max subscription)
SQLite → CRM database (47 tables)
TypeScript orchestrator → cron session manager
[Execution Layer]
Resend API → email sending (verified domain)
MillionVerifier → email validation before send
Playwright + Chrome → platform automation
[Delivery Layer]
Cloudflare Tunnel → ngrok replacement ($0)
Hetzner VPS (CPX21) → always-on infrastructure
Telegram Bot → real-time notifications
Total monthly infrastructure cost: $10.59
n8n Lead Pipeline
The VPS runs n8n v2.9.3 with 9 active workflows. The Google Maps workflow runs every 6 hours, iterating through 157 keyword/location combinations — "plumber New Jersey", "restaurant automation", "HVAC contractor NYC" — and stores raw leads in PostgreSQL.
CREATE TABLE n8n_leads_raw (
id SERIAL PRIMARY KEY,
source VARCHAR(50),
business_name TEXT,
owner_name TEXT,
email TEXT,
phone TEXT,
website TEXT,
industry TEXT,
city TEXT,
state TEXT,
imported_to_crm BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE n8n_search_tasks (
id SERIAL PRIMARY KEY,
keyword TEXT,
location TEXT,
industry TEXT,
status VARCHAR(20) DEFAULT 'pending',
results_count INTEGER DEFAULT 0
);
Local orchestrator sessions pick up pending tasks, enrich with Apollo.io, validate emails with MillionVerifier, score leads 0-100, and import to the CRM database.
Lead Scoring
Every lead gets scored before any outreach. This prevents wasting email credits on junk leads.
function scoreLead(lead: Lead): number {
let score = 0;
if (lead.email && isBusinessDomain(lead.email)) score += 15;
if (lead.website) score += 10;
if (lead.phone) score += 5;
if (lead.company_name) score += 5;
if (TARGET_INDUSTRIES.includes(lead.industry)) score += 15;
if (lead.employee_count > 5 && lead.employee_count < 200) score += 10;
if (lead.google_rating >= 4.0 && lead.review_count >= 20) score += 10;
if (lead.apollo_enriched) score += 10;
return Math.min(score, 100);
}
We ran this against our 549-lead database: 123 high (50+), 336 medium (30-49), 90 low. Email sequences only trigger for high-score leads.
Email Infrastructure
Getting cold email delivered in 2026 requires real infrastructure:
Domain setup:
send.getfluxdata.com → CNAME to Resend's servers
SPF: v=spf1 include:amazonses.com ~all
DKIM: [Resend generates this]
DMARC: v=DMARC1; p=quarantine; rua=mailto:dmarc@getfluxdata.com
The subdomain is critical. If cold email gets marked as spam, your root domain's reputation stays clean for transactional emails.
Warmup schedule (we're on day 8):
- Day 1-3: 5 emails/day
- Day 4-7: 10 emails/day
- Day 8-14: 20 emails/day
- Day 15-30: 40 emails/day
- 30+ days: 80-100 emails/day
Daily cap stored in the database and auto-incremented by the orchestrator.
Redis: The Glue Layer
Redis runs on the VPS handling four things:
- Email rate limiting — enforce daily caps across parallel processes
-
Action cooldowns —
lead:{id}:contactedkey with 72-hour TTL - Circuit breakers — if Resend API fails 3x, open for 6 hours
- Session dedup — multiple AI sessions can't process the same lead simultaneously
export async function checkEmailCooldown(leadId: number): Promise<boolean> {
const key = `lead:${leadId}:email_sent`;
const exists = await redis.exists(key);
return exists === 0;
}
export async function setEmailCooldown(leadId: number): Promise<void> {
const key = `lead:${leadId}:email_sent`;
await redis.setex(key, 72 * 60 * 60, '1'); // 72 hour TTL
}
The AI Orchestration Layer
This is the interesting part. A TypeScript orchestrator (pm2 daemon) fires 35 cron-scheduled sessions daily. Each session spawns a claude -p process with a specific prompt and subset of MCP tools.
9:30 AM → outreach_executor (sends today's emails)
12:00 PM → twitter_engage (posts + engages on X)
12:45 PM → email_crm (processes replies)
4:00 PM → proposal_optimizer (drafts proposals for warm leads)
9:00 PM → night_audit (reviews day, updates strategy)
Sessions share a SQLite database and pass context via session_handoffs table. When morning outreach finds a reply, it inserts a handoff for the afternoon session.
const SESSION_MCP_TIER: Record<string, MCPTier> = {
'outreach_executor': 'light', // needs Gmail only
'twitter_engage': 'engagement', // needs Playwright for browser
'proposal_optimizer': 'light', // needs Gmail + search
'night_audit': 'light', // needs Gmail + search
};
Real Results
After 7 days running this system:
- 549 leads in CRM across 6 industries
- 110 cold emails sent (domain warmup — limited to 15/day)
- 0 cold email replies (new domain, expected)
- 1 client from a single Upwork proposal
The cold email machine works technically. The issue is new-domain reputation. Month 2, with proper warmup, this should produce results.
The bigger lesson: the automation stack is sound but I built the machine before validating the message. One Upwork proposal in 15 minutes outperformed 110 automated emails.
Running It Yourself
- Hetzner VPS (CPX21, $10.59/month)
- n8n self-hosted — Docker Compose on the VPS
- Resend — email sending, 100/day free tier
- Redis — Docker on VPS, standard Redis 7
- Cloudflare Tunnel — free, replaces ngrok for webhooks
Total infrastructure time to set up: 4-6 hours. The business logic takes longer, but the foundation is replicable.
Building revenue automation systems for small businesses at Flux Data Solutions. Free 30-min strategy call: calendly.com/jack-getfluxdata
Top comments (0)