A sending domain loses its DMARC record during a DNS change. Nobody notices until reply rates drop a week later. Meanwhile, a hot inbound lead waits in the same queue as a student who typed "Hi".
Outbound teams fight these small fires every week. I wanted one place that catches them early, so I built OpsPulse. It's a set of n8n workflows, a PostgreSQL database, and a dashboard on Cloudflare Workers.
What it does
It's three tools that share one database and one dashboard:
- DomainPulse checks SPF, DKIM, DMARC, and MX for every sending domain at 07:00 UTC each day
- Lead Router scores inbound leads, sorts them into hot, warm, and cold, and drafts a reply
- Content Flow turns one article into LinkedIn, Twitter/X, and newsletter drafts you approve one by one
- A lead enrichment webhook takes a domain and returns company data, tech stack, and likely contacts
n8n and PostgreSQL run on Railway. The language model is Llama 3.3 70B on Groq's free tier. The dashboard is a single Cloudflare Worker that serves the page and proxies calls to n8n.
Try it
You need an n8n instance, a PostgreSQL database, a Groq API key, and a Cloudflare account. Start with the tables:
git clone https://github.com/NoaTubic/n8n-outbound-infra
cd n8n-outbound-infra
psql "$DATABASE_URL" -f db/schema.sql
psql "$DATABASE_URL" -f db/schema-lead-router.sql
psql "$DATABASE_URL" -f db/schema-content.sql
schema.sql also adds five example domains, so swap them for your own. Then import the JSON files from workflows/ in the n8n editor, add a PostgreSQL and a Groq credential, and activate them. Last, set your n8n URL and a new STATUS_API_KEY in ops-worker/wrangler.toml and deploy the dashboard:
cd ops-worker
npm install
npx wrangler login
npx wrangler deploy
Lead scoring is plain code, on purpose
It's tempting to send every lead to a model and ask "is this good?". I didn't. The score comes from a short Code node with four factors: company size, role, budget, and message length. The top score is 120. Hot is 80 and up, warm is 50 to 79, and cold is everything below.
The model runs after that, as a second opinion. It checks the lead against your ideal customer profile and returns a 1 to 10 fit score, reasons, and concerns. If the call fails, the lead is still saved with its score.
Because the rules are plain JavaScript, you can test them without n8n. I ran the node's code on four sample leads:
VP Marketing, 200-1000, 15k-50k 110 hot
Head of Growth, 50-200, 5k-15k 95 hot
Marketing Manager, 10-50, 1k-5k 60 warm
Student, 1-10, under-1k, "Hi" 25 cold
Each lead also stores its scoring_breakdown, so a salesperson can see why a lead landed where it did. A score you can explain is a score people trust. Hot leads go to sales_urgent, warm ones to sales_queue, and both get a drafted reply. Cold leads go to nurture with no reply.
The same email on the same day is processed once. A double-clicked form doesn't create two leads.
Domain checks with a public DNS API
DomainPulse needs no DNS library. It calls Google's public DNS-over-HTTPS endpoint, which takes no key. You can try the SPF check from a terminal:
curl -s 'https://dns.google/resolve?name=example.com&type=TXT'
The answer is JSON, and one of the TXT records is v=spf1 -all. The workflow looks for a record that starts with v=spf1 and flags +all, which lets anyone send mail as you. DMARC lives at _dmarc.example.com, and DKIM is checked on the google selector.
Each domain then gets a status. Missing SPF, missing MX, or +all is critical. Missing DMARC or DKIM is a warning. The workflow compares each result with the previous check and only writes an alert when something changed, so you don't get the same warning every morning.
When a model call fails
Groq's free tier has rate limits, so calls do fail. The enrichment and content workflows each fire three model calls in parallel, and every one has n8n's continueOnFail turned on.
If one branch fails, the job finishes with status partial and keeps what the other two found. Each error goes into a JSON log column, marked as retryable (429 and 5xx) or permanent (other 4xx). One rate limit never throws away a whole job. Debugging is a single query:
SELECT input_domain, status, error_log
FROM enrichment_jobs
WHERE status IN ('failed', 'partial')
ORDER BY created_at DESC LIMIT 10;
Content drafts work the same way. A failed draft is stored as failed, and the other platforms still show up for review. When you approve the last draft of an article, the article itself is marked approved.
Try it yourself
Start with DomainPulse. Add your own sending domains, run the watchdog once from the n8n editor, and see what it finds. The repo has an architecture doc, notes on every workflow, and a runbook with test requests:
NoaTubic
/
n8n-outbound-infra
Production B2B ops platform — domain health monitoring, inbound lead scoring, and AI content distribution. Built on n8n, Railway, PostgreSQL, and Cloudflare Workers.
OpsPulse
Production B2B operations platform built on n8n, Railway, PostgreSQL, and Cloudflare Workers. Three integrated tools for domain health monitoring, inbound lead routing, and AI-powered content distribution.
What It Does
DomainPulse — Domain Health Monitoring
Automated daily checks of SPF, DKIM, DMARC, and MX records across all sending domains. Detects misconfigurations before they tank deliverability. Add domains and run lead enrichment directly from the dashboard.
Lead Router — Inbound Lead Scoring
Webhook-based lead intake with rule-based scoring (company size, role, budget, message quality). AI-powered ICP matching classifies leads into hot/warm/cold tiers, routes them to the right queue, and generates personalized auto-replies.
Content Flow — Multi-Channel Distribution
Paste an article and get AI-generated drafts for LinkedIn, Twitter/X, and email newsletter. Review drafts side-by-side, approve or reject per platform, and track content status through the pipeline.
Architecture
Cloudflare Workers
|
+-----v-----------+
| OpsPulse | Edge Dashboard
| Dashboard | +…https://ops-pulse.noatubic.workers.dev
Feedback and issues are welcome on GitHub.
Top comments (0)