fix: live cold email stats from CRM in dashboard update
- update-launch-dashboard.py now queries the CRM webhook for real pipeline counts - Goals (Cold Emails Sent, Active Contacts, CRM Contacts) update daily at 03:00 - Campaign cards (Cold Mail, Influencer) rebuilt from operations.json dynamically - CRM contacts badge in header updates from live data - All probe functions replaced with actual CRM query
This commit is contained in:
+7
-7
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"period": "2026-06-09 to 2026-06-16",
|
"period": "2026-06-09 to 2026-06-16",
|
||||||
"updated": "2026-06-17 06:00",
|
"updated": "2026-06-17 08:51",
|
||||||
"visitors": 67,
|
"visitors": 67,
|
||||||
"visitors_trend": {
|
"visitors_trend": {
|
||||||
"dir": "up",
|
"dir": "same",
|
||||||
"change": "+13",
|
"change": "+0",
|
||||||
"pct": "+24%"
|
"pct": "+0%"
|
||||||
},
|
},
|
||||||
"visitors_change": "",
|
"visitors_change": "",
|
||||||
"latest_day": "2026-06-16",
|
"latest_day": "2026-06-16",
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
},
|
},
|
||||||
"pageviews": 361,
|
"pageviews": 361,
|
||||||
"pageviews_trend": {
|
"pageviews_trend": {
|
||||||
"dir": "up",
|
"dir": "same",
|
||||||
"change": "+29",
|
"change": "+0",
|
||||||
"pct": "+9%"
|
"pct": "+0%"
|
||||||
},
|
},
|
||||||
"pageviews_change": "",
|
"pageviews_change": "",
|
||||||
"bounce_rate": "\u2014",
|
"bounce_rate": "\u2014",
|
||||||
|
|||||||
+33
-25
@@ -1,37 +1,37 @@
|
|||||||
{
|
{
|
||||||
"date": "Sunday, June 14, 2026",
|
"date": "Sunday, June 14, 2026",
|
||||||
"updated": "2026-06-17T06:00:32-04:00",
|
"updated": "2026-06-17T08:51:21-04:00",
|
||||||
"goals": [
|
"goals": [
|
||||||
{
|
{
|
||||||
"current": 1,
|
"current": 16,
|
||||||
"note": "CRM: 88 on Hold, 1 Cold (chat lead), 1 sold. Need real emails for outreach."
|
"note": "CRM: 0 Cold, 16 Contacted, 0 Warm. 90 on Hold, 0 Won."
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": 1,
|
|
||||||
"note": "1 paying customer (agent sold)"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": 12,
|
|
||||||
"note": "60 YT creators identified \u2014 12 real emails found via YouTube about page scrape"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"current": 28,
|
|
||||||
"note": "Plausible: 28 unique visitors over 5 days (Jun 9-13). 7 countries. +75% DoD on latest day."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"current": 0,
|
"current": 0,
|
||||||
"note": "0 cold emails sent \u2014 12 real YT creator emails now available. Templates and coupons ready."
|
"note": "0 paying customer(s) (agent sold)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"current": 139,
|
"current": 1,
|
||||||
"note": "89 original + 50 backlink targets (Hold)"
|
"note": "1 YT creators & affiliates identified (90 on Hold)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": 67,
|
||||||
|
"note": "Plausible: 28 unique visitors over 5 days (Jun 9-13). 7 countries. +75% DoD on latest day."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": 16,
|
||||||
|
"note": "16 contacts emailed. 0 replied. 2 bounced."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"current": 833,
|
||||||
|
"note": "833 total in CRM (0 Cold, 16 Contacted, 0 Warm, 90 Hold, 0 Won, 1 Lost, 2 Bounced)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"campaigns": [
|
"campaigns": [
|
||||||
{
|
{
|
||||||
"metrics": [
|
"metrics": [
|
||||||
0,
|
0,
|
||||||
0,
|
16,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"items": [
|
"items": [
|
||||||
@@ -44,14 +44,14 @@
|
|||||||
"ok": true
|
"ok": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "No Cold contacts with real emails yet",
|
"text": "0 Cold contacts, 16 contacted",
|
||||||
"ok": false
|
"ok": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"metrics": [
|
"metrics": [
|
||||||
60,
|
90,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"state_of_play": "Day 7 \u2014 Influencer campaign on hold until end of July. Full focus on Friends & Family battle-testing. 1 agent sold, 28 visitors across 5 days, 7 countries. Battle-proofing the system with real users before scaling to influencers. 39 coworking space leads + 6 e-commerce leads on Hold, ready for F&F outreach.",
|
"state_of_play": "Day 9 \u2014 16 cold emails sent, 0 replies, 2 bounced. 833 CRM contacts (0 Cold/16 Contacted/0 Warm/90 Hold). 0 agent(s) sold.",
|
||||||
"comms": {
|
"comms": {
|
||||||
"inbox": {
|
"inbox": {
|
||||||
"total": 0,
|
"total": 0,
|
||||||
@@ -102,7 +102,15 @@
|
|||||||
"total": 8,
|
"total": 8,
|
||||||
"last35h": 0
|
"last35h": 0
|
||||||
},
|
},
|
||||||
"alerts": 0
|
"alerts": 0,
|
||||||
|
"crm": {
|
||||||
|
"total": 833,
|
||||||
|
"cold": 0,
|
||||||
|
"contacted": 16,
|
||||||
|
"warm": 0,
|
||||||
|
"won": 0,
|
||||||
|
"hold": 90
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"INFLUENCER CAMPAIGN PAUSED until end of July \u2014 focus on F&F battle-testing first",
|
"INFLUENCER CAMPAIGN PAUSED until end of July \u2014 focus on F&F battle-testing first",
|
||||||
@@ -112,5 +120,5 @@
|
|||||||
"1 agent sold already \u2014 good sign for F&F conversion expectations"
|
"1 agent sold already \u2014 good sign for F&F conversion expectations"
|
||||||
],
|
],
|
||||||
"day": 9,
|
"day": 9,
|
||||||
"notes": "Day 9 auto-update at 06:00 UTC"
|
"notes": "Day 9 auto-update at 08:51 UTC"
|
||||||
}
|
}
|
||||||
+42
-64
@@ -693,7 +693,7 @@
|
|||||||
<div class="badge-row">
|
<div class="badge-row">
|
||||||
<span class="badge live">● Site Live</span>
|
<span class="badge live">● Site Live</span>
|
||||||
<span class="badge warn">● Prelaunch</span>
|
<span class="badge warn">● Prelaunch</span>
|
||||||
<span class="badge info">◉ 139 CRM Contacts</span>
|
<span class="badge info" id="crm-badge">◉ 0 CRM Contacts</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="updated" id="brief-updated">Last updated: —</div>
|
<div class="updated" id="brief-updated">Last updated: —</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -878,67 +878,24 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* ════════ DATA ════════ */
|
/* ════════ DATA ════════ */
|
||||||
const goals = [
|
/* Campaign label templates — labels stay here, values come from JSON */
|
||||||
{ id: 'active-contacts', label: 'Active Contacts', current: 1, target: 60, note: 'CRM Cold → Won (88 on Hold excluded)' },
|
const CAMPAIGN_LABELS = [
|
||||||
{ id: 'subscriptions', label: 'Subscriptions', current: 0, target: 50, note: 'Paying customers' },
|
{ l: '✉ Cold Mail', status: 'draft', metricLabels: ['Drafted', 'Sent', 'Replied'] },
|
||||||
{ id: 'influencers', label: 'Influencers', current: 0, target: 20, note: 'YT creators & affiliates' },
|
{ l: '🎙 Influencer', status: 'paused', metricLabels: ['Identified', 'Contacted', 'Onboarded'] },
|
||||||
{ id: 'visitors', label: 'Page Visitors', current: 9, target: 10000, unit: '/mo', note: '9 unique visitors in first 2 days (Plausible)' },
|
{ l: '🔗 LinkedIn', status: 'draft', metricLabels: ['Connections', 'Outreach', 'Replied'] },
|
||||||
{ id: 'outreach-sent', label: 'Cold Emails Sent', current: 0, target: 100, note: 'First touch sent' },
|
|
||||||
{ id: 'crm-contacts', label: 'CRM Contacts', current: 139, target: 200, note: '89 original + 50 backlink targets' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const campaigns = [
|
const goals = [
|
||||||
{
|
{ id: 'active-contacts', label: 'Active Contacts', current: 0, target: 60, note: 'Loading...' },
|
||||||
label: '✉ Cold Mail', status: 'draft',
|
{ id: 'subscriptions', label: 'Subscriptions', current: 0, target: 50, note: 'Loading...' },
|
||||||
metrics: [{ v: 0, l: 'Drafted' }, { v: 0, l: 'Sent' }, { v: 0, l: 'Replied' }],
|
{ id: 'influencers', label: 'Influencers', current: 0, target: 20, note: 'Loading...' },
|
||||||
items: [
|
{ id: 'visitors', label: 'Page Visitors', current: 0, target: 10000, unit: '/mo', note: 'Loading...' },
|
||||||
{ t: 'Coupons ready: friends950, creator950', ok: true },
|
{ id: 'outreach-sent', label: 'Cold Emails Sent', current: 0, target: 100, note: 'Loading...' },
|
||||||
{ t: 'Template signed as Mark', ok: true },
|
{ id: 'crm-contacts', label: 'CRM Contacts', current: 0, target: 200, note: 'Loading...' },
|
||||||
{ t: 'No contacts with real emails yet', ok: false },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '🎙 Influencer', status: 'paused',
|
|
||||||
metrics: [{ v: 60, l: 'Identified' }, { v: 0, l: 'Contacted' }, { v: 0, l: 'Onboarded' }],
|
|
||||||
items: [
|
|
||||||
{ t: '60 YT creators on Hold', ok: true },
|
|
||||||
{ t: 'Need business emails for top 20', ok: false },
|
|
||||||
{ t: 'Affiliate program TBD', ok: false },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '🔗 LinkedIn', status: 'draft',
|
|
||||||
metrics: [{ v: 0, l: 'Connections' }, { v: 0, l: 'Outreach' }, { v: 0, l: 'Replied' }],
|
|
||||||
items: [
|
|
||||||
{ t: 'No outreach started', ok: false },
|
|
||||||
{ t: 'Company page TBD', ok: false },
|
|
||||||
{ t: 'No content strategy defined', ok: false },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '🌐 SEO & Traffic', status: 'draft',
|
|
||||||
metrics: [{ v: 9, l: 'Visitors/mo' }, { v: 0, l: 'Blog Posts' }, { v: 50, l: 'Backlink Targets' }],
|
|
||||||
items: [
|
|
||||||
{ t: 'Plausible analytics live with 7 custom events — 9 visitors, 37 pageviews', ok: true },
|
|
||||||
{ t: '50 backlink targets in CRM (strategy=Backlink outreach)', ok: true },
|
|
||||||
{ t: 'Need Google Search Console submission', ok: false },
|
|
||||||
{ t: 'Need blog posts (guest posts + own content)', ok: false },
|
|
||||||
{ t: 'Need directory submissions: Product Hunt, BetaList, G2 + 12 more', ok: false },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '🔗 Backlinks', status: 'draft',
|
|
||||||
metrics: [{ v: 50, l: 'Collected' }, { v: 0, l: 'Ref Domains' }, { v: 0, l: 'Guest Posts' }],
|
|
||||||
target: { links: 10, domains: 5, posts: 3 },
|
|
||||||
items: [
|
|
||||||
{ t: '50 backlink targets sourced — 15 AI directories, 15 blogs, 20 YT/newsletters', ok: true },
|
|
||||||
{ t: 'All 50 in CRM with strategy=Backlink outreach (Hold stage)', ok: true },
|
|
||||||
{ t: 'Need directory submissions (Product Hunt, BetaList, G2 + 12 more)', ok: false },
|
|
||||||
{ t: 'Need guest post pitches to 15 tech blogs', ok: false },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let campaigns = []; // populated from operations.json via fetchLiveData
|
||||||
|
|
||||||
/* Top 5 needs — 2 done after Oliver's last update */
|
/* Top 5 needs — 2 done after Oliver's last update */
|
||||||
const NEEDS = [
|
const NEEDS = [
|
||||||
{ id: "n1", text: "Business emails for top 20 YouTube creators — blocking cold email outreach", done: false },
|
{ id: "n1", text: "Business emails for top 20 YouTube creators — blocking cold email outreach", done: false },
|
||||||
@@ -1033,17 +990,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderCampaigns() {
|
function renderCampaigns() {
|
||||||
document.getElementById('campaign-grid').innerHTML = campaigns.map(c => {
|
const src = campaigns.length > 0 ? campaigns : [];
|
||||||
const st = c.status === 'active' ? '<span class="status-tag active">● Active</span>'
|
const grid = document.getElementById('campaign-grid');
|
||||||
: c.status === 'paused' ? '<span class="status-tag paused">● Paused</span>'
|
grid.innerHTML = src.map((c, idx) => {
|
||||||
|
const tpl = CAMPAIGN_LABELS[idx] || {};
|
||||||
|
const label = tpl.l || 'Campaign';
|
||||||
|
const st = c.status || tpl.status || 'draft';
|
||||||
|
const statusTag = st === 'active' ? '<span class="status-tag active">● Active</span>'
|
||||||
|
: st === 'paused' ? '<span class="status-tag paused">● Paused</span>'
|
||||||
: '<span class="status-tag draft">Draft</span>';
|
: '<span class="status-tag draft">Draft</span>';
|
||||||
|
const metrics = c.metrics || [0, 0, 0];
|
||||||
|
const labels = tpl.metricLabels || ['', '', ''];
|
||||||
return `<div class="campaign-card">
|
return `<div class="campaign-card">
|
||||||
<h3>${c.label} ${st}</h3>
|
<h3>${label} ${statusTag}</h3>
|
||||||
<div class="stat-row">${c.metrics.map(m => `<div class="stat-item"><div class="stat-num">${m.v}</div><div class="stat-label">${m.l}</div></div>`).join('')}</div>
|
<div class="stat-row">${metrics.map((v, i) => `<div class="stat-item"><div class="stat-num">${v}</div><div class="stat-label">${labels[i] || ''}</div></div>`).join('')}</div>
|
||||||
${c.target ? `<div style="font-size:10px;color:var(--text-muted);margin-bottom:6px;">Target: ${c.target.links} quality links, ${c.target.domains} referring domains, ${c.target.posts} guest posts</div>` : ''}
|
${c.target ? `<div style="font-size:10px;color:var(--text-muted);margin-bottom:6px;">Target: ${c.target.links} quality links, ${c.target.domains} referring domains, ${c.target.posts} guest posts</div>` : ''}
|
||||||
<ul>${c.items.map(i => `<li><span class="dot ${i.ok ? 'green' : 'red'}"></span>${i.t}</li>`).join('')}</ul>
|
<ul>${(c.items || []).map(i => `<li><span class="dot ${i.ok ? 'green' : 'red'}"></span>${i.t}</li>`).join('')}</ul>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
// Fallback if no JSON data loaded
|
||||||
|
if (!grid.innerHTML) {
|
||||||
|
grid.innerHTML = '<div style="color:var(--text-muted);font-size:13px;padding:12px 0;">Loading campaign data...</div>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Needs (checkboxes) ── */
|
/* ── Needs (checkboxes) ── */
|
||||||
@@ -1246,6 +1214,16 @@
|
|||||||
if (d.updated) document.getElementById('brief-updated').textContent = 'Last updated: ' + d.updated;
|
if (d.updated) document.getElementById('brief-updated').textContent = 'Last updated: ' + d.updated;
|
||||||
if (d.goals) d.goals.forEach((g, i) => { if (goals[i]) { goals[i].current = g.current; goals[i].note = g.note || goals[i].note; } });
|
if (d.goals) d.goals.forEach((g, i) => { if (goals[i]) { goals[i].current = g.current; goals[i].note = g.note || goals[i].note; } });
|
||||||
renderGoals();
|
renderGoals();
|
||||||
|
// Load campaigns from operations.json
|
||||||
|
if (d.campaigns && d.campaigns.length > 0) {
|
||||||
|
campaigns = d.campaigns;
|
||||||
|
renderCampaigns();
|
||||||
|
}
|
||||||
|
// Update CRM badge
|
||||||
|
const badge = document.getElementById('crm-badge');
|
||||||
|
if (badge && d.comms && d.comms.crm) {
|
||||||
|
badge.innerHTML = `◉ ${d.comms.crm.total} CRM Contacts`;
|
||||||
|
}
|
||||||
} catch { /* defaults */ }
|
} catch { /* defaults */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user