The Problem
I needed income. I had zero budget, zero technical cofounders, and zero traffic. What I did have was a laptop, an internet connection, and a willingness to build everything from scratch.
Three weeks later: 245 digital products, $12,752 catalog value, automated delivery, self-posting social media daemons, and a store that runs itself.
Total cost: $0/month.
Here's the exact code, architecture, and every API I used.
The Architecture
+-------------------------------------------------------+
| GitHub Pages |
| (ghost-store) |
| +-------------+ +--------------+ +------------+ |
| | index.html | | admin.html | | claim.html | |
| | (245 prods) | | (dashboard) | | (verify) | |
| +-------------+ +--------------+ +------------+ |
+---------------------------+---------------------------+
| HTTPS
+---------------------------v---------------------------+
| Python Delivery Server |
| (localhost:8081) |
| +----------+ +--------+ +---------+ +-----------+ |
| | /ipn | | /claim | | /track | | /admin | |
| |(PayPal) | |(verify)| |(analytics)|(dashboard) | |
| +----------+ +--------+ +---------+ +-----------+ |
+---------------------------+---------------------------+
|
+---------------------------v---------------------------+
| ngrok Tunnel |
| scarily-blatancy-washday.ngrok-free.dev |
+---------------------------+---------------------------+
|
+---------------------------v---------------------------+
| PayPal |
| IPN -> /ipn -> Verify -> Email -> Done |
+-------------------------------------------------------+
## 1. The Product Generator
Every product follows a template. The batch generator creates timestamped folders with product.json and content.txt.
import json, os
from datetime import datetime
def create_product(name, niche, ptype, price):
safe = name.replace("/", "_").replace(":", "_")
folder = f"products/{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}_{safe[:40]}"
os.makedirs(folder, exist_ok=True)
product = {
"product_name": name,
"price": price,
"niche": niche,
"type": ptype,
"generated_at": datetime.utcnow().isoformat(),
}
with open(f"{folder}/product.json", "w") as f:
json.dump(product, f, indent=2)
Each product gets a unique timestamped folder (no collisions), structured JSON (read by the store generator), and Markdown content (readable, indexable).
This runs in batches. One command = 50-100 products.
## 2. The Store Generator
The key insight: the store is a static HTML file. No database. No server-side rendering. Just pure HTML generated from product JSONs.
import json, os
def generate_store(product_dir):
products = []
for folder in os.listdir(product_dir):
json_path = os.path.join(product_dir, folder, "product.json")
if not os.path.exists(json_path):
continue
with open(json_path) as f:
p = json.load(f)
products.append(p)
bundles = [p for p in products if p["type"] == "bundle"]
premium = [p for p in products if p["price"] >= 30 and p["type"] != "bundle"]
standard = [p for p in products if p["price"] < 30]
total_value = sum(p["price"] for p in products)
The generated HTML includes SEO meta tags, Open Graph tags (Facebook preview), Twitter Card tags (X preview), Schema.org structured data (Google rich snippets), custom tracking pixel, newsletter signup form, and every product with its own PayPal.me button.
One command updates the entire store. Adding 100 products takes the same effort as adding 1.
## 3. PayPal IPN Delivery Server
This is the heart of the automation. A Python HTTP server that processes payments and delivers products.
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import json, urllib.request, smtplib
class DeliveryHandler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length).decode()
if self.path == "/ipn":
self.handle_ipn(body)
elif self.path == "/subscribe":
self.handle_subscribe(body)
def handle_ipn(self, body):
verify_data = body + "&cmd=_notify-validate"
req = urllib.request.Request(
"https://ipnpb.paypal.com/cgi-bin/webscr",
data=verify_data.encode(),
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
response = urllib.request.urlopen(req).read().decode()
if response == "VERIFIED":
params = parse_qs(body)
txn_id = params.get("txn_id", [""])[0]
item_name = params.get("item_name", [""])[0]
if self.order_exists(txn_id):
return
order = {
"txn_id": txn_id,
"product": item_name,
"amount": params.get("mc_gross", ["0"])[0],
"status": "paid",
"time": str(datetime.now()),
}
self.save_order(order)
self.email_product(params.get("payer_email", [""])[0], item_name)
def email_product(self, email, product_name):
msg = MIMEText(f"Here's your product: {product_name}\n\nDownload: {STORE}/{product_name}")
msg["Subject"] = f"Your {product_name} is ready!"
msg["From"] = "noreply@ghostprotocol.com"
msg["To"] = email
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login("Nickram17549@gmail.com", GMAIL_APP_PASSWORD)
server.send_message(msg)
Key features: PayPal IPN verification (server confirms payment with PayPal before delivering), duplicate transaction detection (prevents double delivery), email delivery via Gmail SMTP (uses App Password), and JSON file storage (no database needed).
## 4. The X (Twitter) Auto-Poster
This was the hardest bug to fix. React-based apps ignore programmatic clicks.
from playwright.sync_api import sync_playwright
import time
def post_to_x(page, text):
page.goto("https://x.com/compose/post")
time.sleep(3)
# Type the post - keyboard events ARE trusted by React
page.keyboard.insert_text(text)
time.sleep(4) # CRITICAL: React needs time to process
# Submit via Ctrl+Enter - OS-level trusted event
page.keyboard.press("Control+Enter")
time.sleep(3)
return "compose" not in page.url.lower()
**The React isTrusted bug:** When you call dispatchEvent(), JavaScript sets event.isTrusted = false. React checks this flag. If false, React ignores the click entirely.
**The fix:** keyboard.insert_text() and keyboard.press("Control+Enter") generate OS-level input events with isTrusted = true. React processes these normally.
while True:
post = pick_random_post_from_pool()
post_to_x(page, post)
time.sleep(3600 + random.randint(0, 3600)) # Every 1-2 hours
## 5. The Dev.to Auto-Poster
Same Playwright approach, but using the API endpoint via browser fetch (no API key needed).
def post_to_devto(page, article):
page.goto("https://dev.to")
time.sleep(3)
result = page.evaluate(
async function(payload) {
const resp = await fetch("/api/articles", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(payload)
});
const data = await resp.json();
return {status: resp.status, url: data.url, error: data.error};
}, {"article": article}
)
return result.get("status") == 201
Key insight: No API key needed. Playwright's persistent browser context keeps the session alive. Same-origin fetch() requests automatically include auth cookies. Zero configuration.
## 6. What It Costs
| Service | Cost | What It Does |
|---------|------|--------------|
| GitHub Pages | $0 | Hosts storefront |
| ngrok free | $0 | HTTPS tunnel for webhooks |
| Python | $0 | Server and automation |
| PayPal.me | $0 | Payment processing |
| Gmail | $0 | Product delivery email |
| Playwright | $0 | Browser automation |
| Edge browser | $0 | Authenticated session |
| **Total** | **$0/mo** | |
## The Results After 3 Weeks
- 245 products across 60+ niches
- $12,752 catalog value
- $177 revenue from 5 orders (all auto-delivered)
- Auto-posting to X (1-2h) and dev.to (3-6h)
- Auto-start on PC login
- Zero manual work required
## What's Next
The system exists. Every part works. The bottleneck is now purely distribution - getting people to see the products.
The daemons are running 24/7. Every hour, a new post goes out. Every 3-6 hours, a new article. The store regenerates with each batch of products.
If you can build the product, you can build the automation. The code is all here. The architecture is proven. It costs nothing to run.
**Build once. Automate everything. Collect forever.**
---
Want to see the store? [245 Digital Products - $12,752 Catalog](https://EbkBoss.github.io/ghost-store)
Full code available in the Ghost Protocol repository.
---
*Built with Python, Playwright, and pure stubbornness. Zero VCs, zero funding, zero excuses.*
Top comments (0)