A ZoomInfo alternative, built for Almcoe.
Lean, industry-specific intent system for commercial HVAC, commercial refrigeration, cold storage, and rack refrigeration across Almcoe's full Texas footprint — DFW, Austin, and San Antonio. Built around two killer signals ZoomInfo can't match: parsed Schedule Service form submissions from contact@almcoe.com (pure declared intent), and Texas public permit filings (top-of-buying-cycle intent). No pixel dependency, no bidstream black box.
What this system does
Monitors a defined list of target accounts across multiple signal sources, scores buying intent, and alerts your team when an account crosses a threshold. No bidstream black box. Every signal is transparent and auditable.
Why this vs. ZoomInfo Streaming Intent
| Factor | ZoomInfo SI | This System |
|---|---|---|
| Strongest signal | Bidstream — inferred interest | Your own form submissions — declared intent |
| Second signal | Publisher network page views | Texas permit filings — buying-cycle trigger |
| Account universe | 500K+ (most irrelevant) | Your Texas TAM (~100 curated accounts) |
| Industry tuning | Generic B2B topic tree | HVAC / refrigeration / cold storage only |
| Regional tuning | Global | DFW + Austin + SA permits, Texas chains |
| Cost | $40K-50K/year | $500-3K/year |
| Legal exposure | Bidstream sits in GDPR gray zone | Public record + your own inbox only |
Next step
Open the Setup Guide. It walks through the nine tasks to stand this up, in order, with time estimates and vendor links.
Setup Guide
Seven steps. Half the work is done if you just complete steps 1-3.
Open Topic Taxonomy. Six categories pre-loaded: Commercial HVAC, Commercial Refrigeration, Cold Storage, Rack Refrigeration, Design & Installation, Compliance & Regulations. Keywords include brands you install — Hussmann, Hillphoenix, Trane, Carrier, Daikin.
- Add brand names or jargon specific to Almcoe's service mix.
- Remove any topics outside your actual scope.
Open Target Accounts. Now covers full Texas footprint: DFW, Austin, San Antonio. Pre-loaded with HEB (SA HQ), Whataburger (SA HQ), Buc-ee's, Samsung Austin/Taylor, Tesla Giga Texas, Tito's, Toyota SA, Alcon Fort Worth, Dell Round Rock, plus the usual grocery/cold-storage/data-center/healthcare anchors.
- Mark Tier 1 for active pursuit, Tier 2 for monitor, Tier 3 for awareness.
- Add existing clients — you want signals on them too (expansion, champion moves).
- Add any accounts you've lost bids on. Revenge pipeline is real.
This is your single highest-value signal. Open Form Intake.
- When a forwarded submission hits your inbox, paste the email body into the intake tool.
- The parser extracts company name, contact, location, equipment, problem type, emergency flag, warranty flag.
- The system auto-matches to your account list — if it's a known target, it flags Tier 1/2/3 priority.
- If the company is unknown, you can add them as a new account with one click.
- Each submission scores as declared_intent (weight 60) — highest single-signal score in the system.
Level up later: Cloudflare Email Workers can automate this. Set up a forwarding rule from your mailbox → a dedicated address on almcoe.online → a Worker that parses and logs automatically. No pasting required. But for now, manual paste gets you 90% of the value.
Your second-highest signal. HVAC and refrigeration permits are public record. A filed mechanical permit means someone is literally about to build or retrofit — highest possible buying intent.
- Dallas: dallasopendata.com — Socrata API, building permits dataset with MEP breakout.
- Fort Worth: data.fortworthtexas.gov — Socrata API.
- Austin: data.austintexas.gov — Socrata API, "Issued Construction Permits" dataset.
- San Antonio: data.sanantonio.gov — permits dataset via Socrata.
- Plano, Frisco, Arlington, Irving, McKinney, Round Rock, Sugar Land: individual portals, varying quality.
- TDLR: tdlr.texas.gov — refrigeration contractor licensing activity.
Filter by: valuation threshold ($100K+), permit type (commercial mechanical, refrigeration), and property type keywords (grocery, warehouse, restaurant, data center, industrial).
"Champion moves" — when a Facilities Manager you know leaves a client, you want to know their new employer immediately. Same for hiring spikes in MEP/plant-ops at target accounts.
- Coresignal — LinkedIn-derived. Roughly $2-5K/mo at scale.
- Proxycurl — pay-per-lookup, ~$0.01 per profile. Good for low volume.
- Manual fallback: LinkedIn Sales Navigator ($99/mo) with saved lead searches + weekly notification digest. Not automated but effective for 1 person.
Expansion announcements and fundraises often precede permit filings by 3-9 months. Free sources, high signal.
- Google News RSS — free. Per-account alerts on "expansion," "new facility," "groundbreaking," "distribution center." Easy to set up via
https://news.google.com/rss/search?q=CompanyName+expansion. - SEC EDGAR — free. 8-K filings often announce new facilities before press does.
- Crunchbase API — $49-499/mo tiers. Funding rounds, M&A, exec changes.
- Texas Comptroller — new business filings, tax status changes.
Open Signal Scoring. Adjust weights to match your instincts. Defaults:
- Declared intent (form submission) — 60 points
- Permit filing on target account — 40 points
- Known-contact job change — 35 points
- Hot alert threshold — score ≥ 80
Architecture
How data flows from source to signal to alert.
Tech stack recommendation
| Frontend / Dashboard | Single-page app (this one, extended) or Retool / Appsmith |
| Workers / Compute | Cloudflare Workers (you already use CF for DNS) |
| Storage | Cloudflare D1 (SQLite) for small scale, Postgres (Supabase/Neon) for larger |
| Queue | Cloudflare Queues for deferred enrichment |
| Topic Classifier | Claude or GPT via API — classify page/news text against your topic tree |
| Scheduling | Cloudflare Cron Triggers |
| Alerts | Slack incoming webhooks + Resend/SendGrid for email |
Topic Taxonomy
Keywords and phrases that drive classification. Edit freely — these are your first-party intent categories.
Target Account Watchlist
Pre-loaded with Texas North accounts relevant to HVAC, commercial refrigeration, cold storage, and data center cooling. Edit freely.
| Company | Category | Tier | Notes |
|---|
Form Submission Intake ★ HIGHEST VALUE
Convert contact@almcoe.com email forwards into structured intent signals.
Paste a forwarded submission
When an email hits contact@almcoe.com and gets forwarded to you, copy the full email body (everything including field labels) and paste below. The parser looks for field names from the Schedule Service form and extracts structured data.
Logged submissions
| Logged | Company | City | Contact | Type | Match | Score |
|---|
Automation roadmap
Today: manual paste (works now, zero infrastructure)
Forward email → open this tool → paste → parse. Takes 20 seconds per submission.
Next level: Cloudflare Email Workers
Set up an email forwarding rule: contact@almcoe.com → intake@almcoe.online (your domain, Cloudflare-routed) → Email Worker parses and logs automatically. Removes the manual step. Roughly 2 hours to build.
Note: you can't add Email Routing to almcoe.com since you don't control DNS there. But you can set up a forward rule in Gmail/Outlook: "if from: contact@almcoe.com, forward to: intake@almcoe.online". Then Cloudflare Email Workers takes over from intake@almcoe.online.
Fully automated: IMAP watcher on a shared inbox
If corporate gives you access to contact@almcoe.com via IMAP, a Worker can poll the inbox every few minutes and process new submissions with zero human involvement. Requires IT coordination.
Web Pixel Optional / Later
Kept here for future reference. Not part of the current build.
- You launch a public-facing lead-gen site on a domain you control (e.g., almcoe.online with paid traffic)
- Smart Care corporate gives you pixel-level access to a section of their site
- You start hosting proposals on a public URL that prospects access
Standard Pixel
<!-- Almcoe Intent Pixel v1.0 -->
<script>
(function() {
var COLLECTOR_URL = 'https://intent-collector.almcoe.online/event';
var SITE_ID = 'almcoe-main';
function sendEvent(type, extra) {
var payload = {
site: SITE_ID,
event: type,
url: window.location.href,
path: window.location.pathname,
referrer: document.referrer,
title: document.title,
ts: new Date().toISOString(),
session: getSession(),
ua: navigator.userAgent,
lang: navigator.language,
extra: extra || {}
};
// Use sendBeacon when available for reliability
var body = JSON.stringify(payload);
if (navigator.sendBeacon) {
navigator.sendBeacon(COLLECTOR_URL, body);
} else {
fetch(COLLECTOR_URL, { method: 'POST', body: body, keepalive: true });
}
}
function getSession() {
var key = 'almcoe_sid';
var sid = sessionStorage.getItem(key);
if (!sid) {
sid = 'sid_' + Math.random().toString(36).slice(2) + Date.now().toString(36);
sessionStorage.setItem(key, sid);
}
return sid;
}
// Fire pageview
sendEvent('pageview');
// Track deep engagement
var engaged = false;
setTimeout(function(){
if (!engaged) { engaged = true; sendEvent('engaged_30s'); }
}, 30000);
// Track scroll depth
var maxScroll = 0;
window.addEventListener('scroll', function() {
var pct = Math.round((window.scrollY + window.innerHeight) / document.body.scrollHeight * 100);
if (pct > maxScroll + 25) {
maxScroll = pct;
sendEvent('scroll_depth', { depth: pct });
}
}, { passive: true });
// Expose for manual events (e.g., proposal views)
window.almcoeTrack = sendEvent;
})();
</script>
Worker Endpoint (Cloudflare)
Deploy this as a Worker at intent-collector.almcoe.online. It accepts the pixel payload, resolves IP to company via IPinfo, and stores events in D1.
// wrangler.toml:
// name = "intent-collector"
// main = "src/index.js"
// [[d1_databases]]
// binding = "DB"
// database_name = "almcoe_intent"
export default {
async fetch(request, env) {
// CORS
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type'
}
});
}
if (request.method !== 'POST') return new Response('Method not allowed', { status: 405 });
const body = await request.json();
const ip = request.headers.get('CF-Connecting-IP');
const country = request.cf?.country;
// IP to company resolution (cached)
const companyData = await resolveCompany(ip, env);
await env.DB.prepare(
`INSERT INTO events (site, event, url, path, referrer, title, ts, session, ua, ip, country, company_name, company_domain, extra)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).bind(
body.site, body.event, body.url, body.path, body.referrer, body.title,
body.ts, body.session, body.ua, ip, country,
companyData?.name || null, companyData?.domain || null,
JSON.stringify(body.extra || {})
).run();
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }
});
}
};
async function resolveCompany(ip, env) {
// Check cache first
const cached = await env.DB.prepare('SELECT company_name, company_domain FROM ip_cache WHERE ip = ? AND updated > ?')
.bind(ip, Date.now() - 7*24*60*60*1000).first();
if (cached) return { name: cached.company_name, domain: cached.company_domain };
// Call IPinfo
const resp = await fetch(`https://ipinfo.io/${ip}?token=${env.IPINFO_TOKEN}`);
const data = await resp.json();
const company = data.company || {};
await env.DB.prepare('INSERT OR REPLACE INTO ip_cache (ip, company_name, company_domain, updated) VALUES (?, ?, ?, ?)')
.bind(ip, company.name || null, company.domain || null, Date.now()).run();
return { name: company.name, domain: company.domain };
}
D1 Schema
CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, site TEXT, event TEXT, url TEXT, path TEXT, referrer TEXT, title TEXT, ts TEXT, session TEXT, ua TEXT, ip TEXT, country TEXT, company_name TEXT, company_domain TEXT, extra TEXT, created_at INTEGER DEFAULT (unixepoch()) ); CREATE INDEX idx_events_company ON events(company_domain); CREATE INDEX idx_events_ts ON events(ts); CREATE INDEX idx_events_session ON events(session); CREATE TABLE ip_cache ( ip TEXT PRIMARY KEY, company_name TEXT, company_domain TEXT, updated INTEGER ); CREATE TABLE signals ( id INTEGER PRIMARY KEY AUTOINCREMENT, account_domain TEXT, account_name TEXT, signal_type TEXT, -- pixel, permit, hire, funding, news, etc. topic TEXT, score INTEGER, source TEXT, detail TEXT, ts TEXT, created_at INTEGER DEFAULT (unixepoch()) ); CREATE INDEX idx_signals_account ON signals(account_domain); CREATE INDEX idx_signals_ts ON signals(ts);
Data Sources
Every API, feed, and data vendor — with real costs and what each one unlocks.
First-party (yours)
| Source | What it unlocks | Cost | Priority |
|---|---|---|---|
| contact@almcoe.com inbox | Schedule Service form submissions = declared intent | Free | ★ Critical |
| Email open/click (outbound) | Engagement on your sent sequences | Included in ESP | Built-in |
| CRM stage changes | Pipeline progression as a signal | Free | High |
| Historical client list | Champion tracking when they move employers | Free | High |
Third-party (paid)
| Source | What it unlocks | Cost | Priority |
|---|---|---|---|
| IPinfo | IP → company resolution for pixel data | $500-1500/mo | Critical |
| Coresignal | LinkedIn job changes, hiring data | $2-5K/mo | High |
| Proxycurl | Per-profile LinkedIn lookups | Pay-per-use, ~$0.01/lookup | Alt to Coresignal |
| Crunchbase API | Funding rounds, M&A, exec changes | $49-499/mo tiers | High |
| ConstructConnect | National construction project feed w/ MEP breakout | $3-10K/yr | High |
| Dodge Data | Alternative construction feed | $5-15K/yr | Alternative |
Public record (free, scraping required)
| Source | What it unlocks | URL |
|---|---|---|
| City of Dallas Open Data | Building & MEP permits (Socrata API) | dallasopendata.com |
| City of Fort Worth Open Data | Building & MEP permits (Socrata API) | data.fortworthtexas.gov |
| City of Austin Open Data | Issued Construction Permits (Socrata API) | data.austintexas.gov |
| City of San Antonio Open Data | Building permits dataset | data.sanantonio.gov |
| Plano, Frisco, McKinney, Arlington, Irving | Individual permit portals | Each city portal |
| Round Rock, Cedar Park, Georgetown | Austin metro suburbs | Each city portal |
| TDLR | HVAC/R licensing & complaint data | tdlr.texas.gov |
| SEC EDGAR | 8-K filings announcing new facilities | sec.gov/edgar |
| Google News RSS | Per-account news alerts | news.google.com/rss |
| Texas Comptroller | Corporate filings, tax status, new business registrations | comptroller.texas.gov |
Signal Scoring
Weight each signal type. An account's intent score is the weighted sum of its signals over a 60-day rolling window, with recency decay.
Base Signal Weights
How many points each signal adds to an account's score when detected.
Multipliers
Alert Thresholds
Scoring formula reference
score(account) = Σ over signals in last 60 days:
weight(signal_type)
× topic_match(signal_topic, tracked_topics)
× tier_multiplier(account.tier)
× recency_decay(signal.ts)
× known_contact_boost(account.has_contact ? 1.3 : 1.0)
recency_decay(ts) = exp(-(today - ts) / 30) // half-life ~21 days
Workflows & Alerts
What happens when a signal fires.
Alert routing
| Trigger | Action | Owner |
|---|---|---|
| New permit filed at Tier 1 account | Immediate Slack + SMS | Senior BD |
| Account score crosses hot threshold | Immediate Slack | Assigned AE |
| Known-contact job change | Slack DM + CRM note | Original relationship owner |
| Funding announcement at Tier 1-2 account | Slack + calendar task for follow-up in 2 weeks | Senior BD |
| Site visit from Tier 1 account | Slack notification with pages viewed | Assigned AE |
| Score in warm range | Daily digest email (8 AM) | BD team |
| Score in cool range | Weekly digest (Monday 8 AM) | BD team |
Slack webhook example
// Cloudflare Worker — fires when a signal causes score to cross threshold
async function sendSlackAlert(env, signal, account) {
const payload = {
blocks: [
{
type: 'header',
text: { type: 'plain_text', text: `🔥 Intent signal: ${account.name}` }
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Signal:* ${signal.type}` },
{ type: 'mrkdwn', text: `*Topic:* ${signal.topic}` },
{ type: 'mrkdwn', text: `*Score:* ${account.score} (was ${account.prev_score})` },
{ type: 'mrkdwn', text: `*Tier:* ${account.tier}` }
]
},
{
type: 'section',
text: { type: 'mrkdwn', text: `*Detail:* ${signal.detail}` }
},
{
type: 'actions',
elements: [
{ type: 'button', text: { type: 'plain_text', text: 'Open in CRM' }, url: account.crm_url },
{ type: 'button', text: { type: 'plain_text', text: 'Mark Pursuing' }, value: `pursue_${account.id}` }
]
}
]
};
await fetch(env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
Cost Model
Real numbers across three build levels. Without the pixel/IPinfo layer, everything gets cheaper.
Level 1 — Free Tier (Manual Paste) $0-500/yr
| This HTML control center (you're using it) | Free |
| Manual form intake via paste-and-parse | Free |
| Google News RSS feeds per account | Free |
| SEC EDGAR monitoring | Free |
| Manual permit portal checking (Dallas + Austin + SA + FW) | Free |
| LinkedIn Sales Navigator (optional) | ~$100/mo |
| Total | $0-$1,200/yr |
Delivers: form submissions as declared-intent signals, news/expansion alerts, manual permit checks. Good starting point.
Level 2 — Automated (Recommended) $2-5K/yr
| Cloudflare Workers + D1 (form parser, permit scraper) | Free tier |
| Cloudflare Email Workers (automated form ingestion) | Free tier |
| Claude/GPT API for parsing + classification | $300-600/yr |
| Permit scraper (Dallas, FW, Austin, SA via Socrata APIs) | Free (APIs), ~6 hrs build |
| Crunchbase Starter API | $1,000/yr |
| Proxycurl LinkedIn lookups (capped) | $1,000-2,000/yr |
| Slack / Resend for alerts | $300/yr |
| Total | ~$2,500-4,000/yr |
Delivers: fully automated form intake, automated permit monitoring, daily news/funding alerts, LinkedIn job-change tracking. This is the sweet spot.
Level 3 — Enterprise $15-25K/yr
| Level 2 stack, upgraded tiers | $4,000 |
| Coresignal (real-time LinkedIn workforce data) | $12,000 |
| ConstructConnect (national construction w/ MEP) | $5,000 |
| Crunchbase Enterprise | $3,000 |
| Total | ~$20-24,000/yr |
Still under half of ZoomInfo pricing, with better industry+regional tuning.
Export / Backup
All configuration lives in your browser's localStorage. Export regularly.
Export configuration
Downloads a JSON file with all your topics, accounts, scoring weights, and thresholds.
Import configuration
Upload a previously exported JSON file.
Reset everything
Wipes all local data and restores defaults.