Add sales automation stack: strategy docs, MCP scripts, PWA refinements
- SALES_STRATEGY.md: $1K/day plan (37 orders @ $27.50 AOV, 1,700 visits) - TRIPWIRE_FUNNEL.md: 7-email Mautic sequence + lead scoring - MONETIZATION.md: 9 monetization options for Nūr PWA (no ads, no registration) - EXECUTABLE_PLAN.md: MCP-executable automation plan - DAILY_OPS.md: daily KPI checklist (visits, orders, sends, recoveries) - scripts/: automation.py, mautic_setup.py, gumroad_setup.py, scout_scrape.py, analytics.py, crontab - quality_leads.json/.csv: 12 enriched Islamic-tech leads for Mautic import - content_queue.json: 10 SEO blog posts for Ghost - PWA: icons, sw.js, e2e + vitest coverage, generator scripts
This commit is contained in:
Executable
+213
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
One-time Mautic Setup: Create segment, import contacts, create 7 emails.
|
||||
Run once: python scripts/mautic_setup.py
|
||||
All operations in a single process to avoid session expiry.
|
||||
"""
|
||||
|
||||
import os, sys, json, csv, re
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.insert(0, "/Users/wmj24/.hermes/mcp-servers/mautic-server")
|
||||
from server import _web_login, _session, BASE, API_TIMEOUT
|
||||
import requests as req
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import urljoin
|
||||
|
||||
# ─── Login ──────────────────────────────────────────────────
|
||||
print("Logging into Mautic...")
|
||||
result = _web_login()
|
||||
if not result.get("success"):
|
||||
print(f"Login failed: {result}")
|
||||
sys.exit(1)
|
||||
print("Logged in.")
|
||||
|
||||
session = req.Session()
|
||||
if _session.get("cookies"):
|
||||
session.cookies.update(_session["cookies"])
|
||||
|
||||
csrf_token = _session.get("csrf", "")
|
||||
|
||||
# ─── 1. Create Segment ──────────────────────────────────────
|
||||
print("\n--- Creating Segment ---")
|
||||
|
||||
def create_segment(name, alias, description):
|
||||
r = session.get(urljoin(BASE, "/s/segments/new"), timeout=20, verify=False)
|
||||
soup = BeautifulSoup(r.text, "lxml")
|
||||
token_input = soup.find("input", {"name": "segment[_token]"})
|
||||
if not token_input:
|
||||
print(" ERROR: Could not find segment[_token]")
|
||||
# Try to find mauticAjaxCsrf
|
||||
m = re.search(r"mauticAjaxCsrf\s*=\s*['\"]([^'\"]+)['\"]", r.text)
|
||||
csrf = m.group(1) if m else csrf_token
|
||||
print(f" Got CSRF: {csrf[:20]}")
|
||||
print(f" Page title check: {'login' in r.text.lower()}")
|
||||
return None
|
||||
|
||||
form_token = token_input.get("value", "")
|
||||
m = re.search(r"mauticAjaxCsrf\s*=\s*['\"]([^'\"]+)['\"]", r.text)
|
||||
ajax_csrf = m.group(1) if m else csrf_token
|
||||
|
||||
r2 = session.post(
|
||||
urljoin(BASE, "/s/segments/new"),
|
||||
data={
|
||||
"segment[name]": name,
|
||||
"segment[alias]": alias,
|
||||
"segment[description]": description,
|
||||
"segment[isPublished]": "1",
|
||||
"segment[_token]": form_token,
|
||||
"segment[buttons][save]": "",
|
||||
},
|
||||
headers={
|
||||
"User-Agent": "hermes-mautic-mcp/1.0",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRF-Token": ajax_csrf,
|
||||
},
|
||||
timeout=API_TIMEOUT,
|
||||
verify=False,
|
||||
)
|
||||
if r2.status_code == 200:
|
||||
# Try to extract segment ID
|
||||
m = re.search(r'/s/segment/(\d+)', r2.text)
|
||||
seg_id = m.group(1) if m else "unknown"
|
||||
print(f" Segment created: {name} (ID: {seg_id})")
|
||||
return seg_id
|
||||
else:
|
||||
print(f" Segment creation failed: {r2.status_code}")
|
||||
print(f" Response: {r2.text[:500]}")
|
||||
return None
|
||||
|
||||
segment_id = create_segment(
|
||||
"High-Value Islamic Tech Leads",
|
||||
"high-value-islamic-tech-leads",
|
||||
"Quality Muslim developers, founders, and builders in Islamic tech — scraped via Scout MCP"
|
||||
)
|
||||
|
||||
# ─── 2. Import Contacts ─────────────────────────────────────
|
||||
print("\n--- Importing Contacts ---")
|
||||
|
||||
leads_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "quality_leads.json")
|
||||
if not os.path.exists(leads_path):
|
||||
print(f" No leads file found at {leads_path}")
|
||||
else:
|
||||
with open(leads_path) as f:
|
||||
leads = json.load(f)
|
||||
|
||||
imported = 0
|
||||
for lead in leads:
|
||||
email = lead.get("email", "")
|
||||
if not email or "@" not in email:
|
||||
# Try to guess email from patterns
|
||||
name_parts = lead.get("full_name", lead.get("username", "")).split()
|
||||
username = lead.get("username", "")
|
||||
domain = lead.get("website", "").replace("https://", "").replace("http://", "").split("/")[0]
|
||||
if domain and domain != "":
|
||||
email = f"{username}@{domain}"
|
||||
else:
|
||||
continue
|
||||
|
||||
# Use Mautic API to create contact
|
||||
# First get CSRF token for contact form
|
||||
try:
|
||||
r = session.get(urljoin(BASE, "/s/contacts/new"), timeout=20, verify=False)
|
||||
soup = BeautifulSoup(r.text, "lxml")
|
||||
token_input = soup.find("input", {"name": "contact[_token]"})
|
||||
if token_input:
|
||||
form_token = token_input.get("value", "")
|
||||
m = re.search(r"mauticAjaxCsrf\s*=\s*['\"]([^'\"]+)['\"]", r.text)
|
||||
ajax_csrf = m.group(1) if m else csrf_token
|
||||
|
||||
r2 = session.post(
|
||||
urljoin(BASE, "/s/contacts/new"),
|
||||
data={
|
||||
"contact[firstname]": lead.get("firstname", ""),
|
||||
"contact[lastname]": lead.get("lastname", ""),
|
||||
"contact[email]": email,
|
||||
"contact[company]": lead.get("company", ""),
|
||||
"contact[position]": lead.get("position", ""),
|
||||
"contact[website]": lead.get("website", ""),
|
||||
"contact[points]": lead.get("lead_score", 0),
|
||||
"contact[_token]": form_token,
|
||||
"contact[buttons][save]": "",
|
||||
},
|
||||
headers={
|
||||
"User-Agent": "hermes-mautic-mcp/1.0",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRF-Token": ajax_csrf,
|
||||
},
|
||||
timeout=API_TIMEOUT,
|
||||
verify=False,
|
||||
)
|
||||
if r2.status_code == 200:
|
||||
imported += 1
|
||||
print(f" Imported: {lead.get('username', email)}")
|
||||
else:
|
||||
print(f" Failed: {lead.get('username', email)} ({r2.status_code})")
|
||||
except Exception as e:
|
||||
print(f" Error importing {email}: {e}")
|
||||
|
||||
print(f" Total imported: {imported}/{len(leads)}")
|
||||
|
||||
# ─── 3. Create Email Sequence ───────────────────────────────
|
||||
print("\n--- Creating Email Sequence ---")
|
||||
|
||||
def create_email(name, subject, body_html, segment_id=0):
|
||||
r = session.get(urljoin(BASE, "/s/emails/new"), timeout=20, verify=False)
|
||||
soup = BeautifulSoup(r.text, "lxml")
|
||||
token_input = soup.find("input", {"name": "email[_token]"})
|
||||
if not token_input:
|
||||
print(f" ERROR: Could not find email[_token] for {name}")
|
||||
return None
|
||||
|
||||
form_token = token_input.get("value", "")
|
||||
m = re.search(r"mauticAjaxCsrf\s*=\s*['\"]([^'\"]+)['\"]", r.text)
|
||||
ajax_csrf = m.group(1) if m else csrf_token
|
||||
|
||||
r2 = session.post(
|
||||
urljoin(BASE, "/s/emails/new"),
|
||||
data={
|
||||
"email[name]": name,
|
||||
"email[subject]": subject,
|
||||
"email[customHtml]": body_html,
|
||||
"email[fromName]": "FalahOS",
|
||||
"email[fromAddress]": "hermes@falahos.my",
|
||||
"email[emailType]": "template",
|
||||
"email[isPublished]": "1",
|
||||
"email[_token]": form_token,
|
||||
"email[buttons][save]": "",
|
||||
},
|
||||
headers={
|
||||
"User-Agent": "hermes-mautic-mcp/1.0",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRF-Token": ajax_csrf,
|
||||
},
|
||||
timeout=API_TIMEOUT,
|
||||
verify=False,
|
||||
)
|
||||
if r2.status_code == 200:
|
||||
print(f" Email created: {name} — '{subject}'")
|
||||
return True
|
||||
else:
|
||||
print(f" Email creation failed for {name}: {r2.status_code}")
|
||||
return False
|
||||
|
||||
# Email definitions (simplified — full HTML in TRIPWIRE_FUNNEL.md)
|
||||
emails = [
|
||||
("Day0-Welcome", "Your Islamic Dev Toolkit is ready 🕌", "<p>Welcome! Here's your free toolkit...</p>"),
|
||||
("Day2-CaseStudy", "How Gading built quran-api (2.5k ⭐)", "<p>Read the story...</p>"),
|
||||
("Day5-Tripwire", "Islamic UI Kit for $10", "<p>Stop designing from scratch...</p>"),
|
||||
("Day8-Objections", "I'm not a designer — neither were they", "<p>Design isn't your job...</p>"),
|
||||
("Day11-Core", "The complete stack — everything you need", "<p>From idea to launched...</p>"),
|
||||
("Day14-FAQ", "Questions? (24-hour bonus)", "<p>Quick answers...</p>"),
|
||||
("Day18-Enterprise", "Building for an organization?", "<p>Enterprise licensing...</p>"),
|
||||
]
|
||||
|
||||
for name, subject, body in emails:
|
||||
create_email(name, subject, body, segment_id or 0)
|
||||
|
||||
print("\n--- Setup Complete ---")
|
||||
print("Next steps:")
|
||||
print("1. Verify segment and contacts in Mautic UI")
|
||||
print("2. Verify emails are published")
|
||||
print("3. Set up automation rules in Mautic UI (email → delay → next email)")
|
||||
print("4. Run automation.py daily via cron")
|
||||
Reference in New Issue
Block a user