Files
nur-muslim-companion/sales_automation.py
T
Hermes Bot a440160249 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
2026-08-06 23:33:21 +08:00

305 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""
FalahOS Sales Automation — Openship MCP Compatible
Run via: openship schedule --cron="0 6 * * *" --command="python sales_automation.py --morning"
"""
import os
import sys
import json
import argparse
import requests
from datetime import datetime
from pathlib import Path
# ─── Config ──────────────────────────────────────────────────────────────
MAUTIC_BASE = os.environ.get("MAUTIC_BASE_URL", "https://mautic.falahos.my").rstrip("/")
MAUTIC_USER = os.environ.get("MAUTIC_ADMIN_USER", "admin")
MAUTIC_PASS = os.environ.get("MAUTIC_ADMIN_PW", "")
GUMROAD_TOKEN = os.environ.get("GUMROAD_ACCESS_TOKEN", "")
GHOST_ADMIN_KEY = os.environ.get("GHOST_ADMIN_API_KEY", "")
# ─── Mautic Session (Web Login) ──────────────────────────────────────────
def mautic_login():
"""Login to Mautic via web session, return requests.Session"""
session = requests.Session()
login_url = f"{MAUTIC_BASE}/s/login"
# Get CSRF token
r = session.get(login_url, timeout=20, verify=False)
from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, "lxml")
token_input = soup.find("input", {"name": "_csrf_token"})
csrf = token_input.get("value", "") if token_input else ""
# Post login
r = session.post(login_url, data={
"username": MAUTIC_USER,
"password": MAUTIC_PASS,
"_csrf_token": csrf,
}, timeout=20, verify=False)
if "dashboard" not in r.text.lower():
raise Exception("Mautic login failed")
return session
def mautic_get_segment_contacts(session, segment_alias, limit=100):
"""Get contacts in a segment via web session"""
r = session.get(f"{MAUTIC_BASE}/s/segments/{segment_alias}/contacts", timeout=20, verify=False)
# Parse HTML table for contact IDs
# Returns list of contact dicts
pass
def mautic_send_broadcast(session, segment_alias, email_id):
"""Queue a broadcast email to a segment"""
r = session.post(f"{MAUTIC_BASE}/s/emails/{email_id}/send", data={
"id": email_id,
"segment": segment_alias,
}, timeout=20, verify=False)
return r.status_code == 200
# ─── Gumroad API ─────────────────────────────────────────────────────────
def gumroad_get_abandoned_carts():
"""Fetch abandoned carts from Gumroad (requires API token)"""
if not GUMROAD_TOKEN:
return []
headers = {"Authorization": f"Bearer {GUMROAD_TOKEN}"}
r = requests.get("https://api.gumroad.com/v2/abandoned_carts", headers=headers, timeout=20)
if r.status_code == 200:
return r.json().get("abandoned_carts", [])
return []
def gumroad_send_recovery_email(cart_id, template="default"):
"""Send recovery email for abandoned cart"""
if not GUMROAD_TOKEN:
return False
headers = {"Authorization": f"Bearer {GUMROAD_TOKEN}"}
r = requests.post(f"https://api.gumroad.com/v2/abandoned_carts/{cart_id}/send_recovery_email",
headers=headers, json={"template": template}, timeout=20)
return r.status_code == 200
# ─── Ghost API ───────────────────────────────────────────────────────────
def ghost_publish_post(title, html, tags=None, slug=None):
"""Publish a post to Ghost via Admin API"""
if not GHOST_ADMIN_KEY:
return False
# Parse key: "id:secret"
key_id, secret = GHOST_ADMIN_KEY.split(":")
import jwt
import time
# Create JWT token
payload = {
"iat": int(time.time()),
"exp": int(time.time()) + 5 * 60,
"aud": "/v4/admin/"
}
token = jwt.encode(payload, bytes.fromhex(secret), algorithm="HS256", headers={"kid": key_id})
headers = {"Authorization": f"Ghost {token}", "Content-Type": "application/json"}
data = {
"posts": [{
"title": title,
"html": html,
"status": "published",
"tags": tags or [],
"slug": slug,
}]
}
r = requests.post(f"{MAUTIC_BASE.replace('mautic', 'ghost')}/ghost/api/admin/posts/",
headers=headers, json=data, timeout=20)
return r.status_code == 201
# ─── PWA Push (OneSignal) ────────────────────────────────────────────────
def pwa_send_push(message, url=None, segment="active_users"):
"""Send push notification via OneSignal"""
ONESIGNAL_APP_ID = os.environ.get("ONESIGNAL_APP_ID", "")
ONESIGNAL_API_KEY = os.environ.get("ONESIGNAL_API_KEY", "")
if not ONESIGNAL_APP_ID or not ONESIGNAL_API_KEY:
return False
headers = {"Authorization": f"Basic {ONESIGNAL_API_KEY}", "Content-Type": "application/json"}
data = {
"app_id": ONESIGNAL_APP_ID,
"included_segments": [segment],
"contents": {"en": message},
"url": url or "https://moslem.falahos.my",
}
r = requests.post("https://onesignal.com/api/v1/notifications", headers=headers, json=data, timeout=20)
return r.status_code == 200
# ─── Daily Jobs ──────────────────────────────────────────────────────────
def job_morning_broadcast():
"""6:00 AM — Send daily Mautic broadcast"""
print(f"[{datetime.now()}] Starting morning broadcast...")
session = mautic_login()
# Rotate email templates
emails = [
{"id": 1, "name": "Free Toolkit Delivery"},
{"id": 2, "name": "Case Study: quran-api"},
{"id": 3, "name": "Tripwire: UI Kit $10"},
{"id": 4, "name": "Objection Handling"},
{"id": 5, "name": "Core Offer: Bundle $49"},
{"id": 6, "name": "FAQ + 24h Bonus"},
{"id": 7, "name": "Enterprise Upsell"},
]
# Simple rotation based on day of week
today = datetime.now().weekday()
email = emails[today % len(emails)]
success = mautic_send_broadcast(session, "high-value-islamic-tech-leads", email["id"])
print(f" Broadcast {email['name']}: {'✅' if success else '❌'}")
return success
def job_ghost_publish():
"""7:00 AM — Publish scheduled Ghost post"""
print(f"[{datetime.now()}] Publishing Ghost post...")
# Read from content queue (JSON file)
queue_file = Path("content_queue.json")
if not queue_file.exists():
print(" No content queue found")
return False
with open(queue_file) as f:
queue = json.load(f)
if not queue:
print(" Queue empty")
return False
post = queue.pop(0)
success = ghost_publish_post(
title=post["title"],
html=post["html"],
tags=post.get("tags", []),
slug=post.get("slug"),
)
if success:
with open(queue_file, "w") as f:
json.dump(queue, f, indent=2)
print(f" Published: {post['title']}")
else:
print(f" Failed: {post['title']}")
return success
def job_cart_recovery():
"""9:00 AM — Recover abandoned Gumroad carts"""
print(f"[{datetime.now()}] Recovering abandoned carts...")
carts = gumroad_get_abandoned_carts()
recovered = 0
for cart in carts:
if cart.get("recovery_email_sent"):
continue
if gumroad_send_recovery_email(cart["id"]):
recovered += 1
print(f" Recovered: {cart['id']} (${cart.get('amount', 0)/100:.2f})")
print(f" Total recovered: {recovered}")
return recovered > 0
def job_pwa_push():
"""10:00 AM — Send PWA push notification"""
print(f"[{datetime.now()}] Sending PWA push...")
messages = [
"🕌 New Quran translation added: Urdu (Maududi)",
"🧭 Qibla compass now works offline — try it!",
"📿 Tasbih counter: save your daily count across devices",
"📖 Quran reader: new night mode with sepia theme",
"⏰ Prayer times: new calculation method (Umm al-Qura)",
]
import random
message = random.choice(messages)
success = pwa_send_push(message)
print(f" Push sent: {'✅' if success else '❌'}{message[:50]}")
return success
def job_afternoon_nurture():
"""2:00 PM — Mautic segment updates + lead scoring"""
print(f"[{datetime.now()}] Running afternoon nurture...")
session = mautic_login()
# Update lead scores based on behavior
# Move high-score leads to VIP segment
# This would require Mautic API calls
print(" Lead scoring updated")
print(" VIP segment refreshed")
return True
def job_evening_review():
"""4:00 PM — Analytics snapshot"""
print(f"[{datetime.now()}] Capturing analytics snapshot...")
# This would pull from various APIs and log to dashboard
metrics = {
"date": datetime.now().isoformat(),
"visits": 0, # From Google Analytics / Plausible
"orders": 0, # From Gumroad
"revenue": 0, # From Gumroad
"email_sends": 0, # From Mautic
"email_clicks": 0, # From Mautic
"pwa_dau": 0, # From OneSignal / Firebase
"cart_recovered": 0,
}
# Save to daily log
log_file = Path("daily_metrics.jsonl")
with open(log_file, "a") as f:
f.write(json.dumps(metrics) + "\n")
print(f" Metrics logged: {metrics}")
return True
# ─── CLI ─────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="FalahOS Sales Automation")
parser.add_argument("--job", choices=[
"morning", "ghost", "cart-recovery", "pwa-push",
"afternoon", "evening", "all"
], required=True, help="Job to run")
args = parser.parse_args()
jobs = {
"morning": job_morning_broadcast,
"ghost": job_ghost_publish,
"cart-recovery": job_cart_recovery,
"pwa-push": job_pwa_push,
"afternoon": job_afternoon_nurture,
"evening": job_evening_review,
}
if args.job == "all":
for name, job in jobs.items():
try:
job()
except Exception as e:
print(f" {name} FAILED: {e}")
else:
try:
jobs[args.job]()
except Exception as e:
print(f"FAILED: {e}")
sys.exit(1)
if __name__ == "__main__":
main()