https://youtu.be/M-yvmK2p54U?si=-pUwIbfluHr2f9cV (DEMO VIDEO)
https://frontend-production-008a.up.railway.app/
An AI-driven Reddit marketing agent: point it at a product, and it discovers relevant subreddits, writes posts and comments grounded in each community's real rules and tone, runs every draft through a multi-stage quality/trust review, and schedules approved content with anti-ban-aware pacing — all through a plain Streamlit UI backed by a FastAPI service.
| Layer | Tech | Role |
|---|---|---|
| Frontend | Streamlit 1.40 | Campaign wizard, dashboard, campaign detail/monitoring — plain Python, no JS build step |
| Backend | FastAPI 0.115 + Uvicorn | Every AI agent, business logic, and the scheduler, exposed as a REST API |
| ORM / DB | SQLAlchemy 2.0 + psycopg2 on Postgres | Campaigns, drafts, reputation, schedule queue, execution logs |
| Scheduler | APScheduler | In-process background job claiming/executing due schedule items |
| Posting | Zernio | Managed, OAuth-backed Reddit API — real API calls, not browser automation or scraping |
| LLM | Google Gemini or OpenRouter (configurable) | Every generation/review agent, via a shared ModelManager with primary/fallback model + retry |
| Auth | passlib/bcrypt | Password hashing for app-level user accounts |
| Secrets at rest | cryptography (Fernet) |
Encrypts stored Reddit OAuth tokens and (archived) browser session files |
| HTTP clients | httpx, requests | Backend→Zernio/LLM calls, and browser-side website fetching (BeautifulSoup for parsing) |
| Hosting | Railway | Two containers (backend, frontend) + one Postgres plugin, deployed via Railpack |
Both services deploy to Railway as two independent containers behind one shared Postgres instance — see Deployment below.
- Website Analysis — extracts company, products, audience, pain points, keywords, and brand voice from a URL or plain description.
- ICP Agent — builds a target-customer profile from that analysis.
- Subreddit Discovery — finds candidates two ways at once: a curated dataset with real, fetched rules (interpreted by the Community Rule Interpreter, which never invents a rule it wasn't given), plus an LLM brainstorm step for real subreddits outside that dataset. The latter are honestly flagged
rules_verified: falserather than given fabricated rules. - Content Generation — Post/Comment Generation agents write drafts grounded in the website analysis, the subreddit's real rules and tone, past successful examples from that community, and the account's own writing voice (Voice Memory).
- Review pipeline — every draft runs through:
- Community Fit Reviewer (Conversation Context) — naturalness, tone fit, rule violations, and a request-vs-share check (flags pure recommendation-request posts on subreddits whose real rules discourage them)
- Promotion Detector — catches and can rewrite overt self-promotional language
- Duplicate Check — blocks repeated/near-identical content
- Trust Decision (Community Intelligence Layer) — a 3-persona Community Simulation (Moderator, Experienced Member, First-time Reader) plus a weighted trust score and recommended action
- Posting Policy — reputation-tiered daily caps and minimum action gaps, subreddit-fit hard-fail/soft-risk checks
- Launch & Schedule — active days, timezone, and send window; approved content gets a concrete send time jittered to a random minute inside that window.
- Scheduler — claims due items (
FOR UPDATE SKIP LOCKED), enforces the account's Posting Policy gap in real time, then asks the Timing Advisor one more live judgment call ("does this moment look safe to post") before actually sending. - Verification & Reflection — checks back at +30 min / +24 h / +72 h on every send; outcomes feed into that subreddit's Community DNA (
avoid_patterns/successful_patterns) and the account's rolling health score.
Every safeguard below exists because of a specific, real failure mode, not as generic hardening — several were added or corrected after tracing back an actual removed post or a real drift in scheduled send times during live testing.
Execution-level
- Real API posting via Zernio — no headless browser, nothing to fingerprint as scripted browser behavior
- Randomized send-time jitter — a random minute inside the chosen window, never the same minute daily
- Timing Advisor — one more live LLM judgment call ("does this moment look safe to post") right before every send, independent of the fixed schedule
Account-level
- Reputation tiers 0-4 (
posting_policy_service.py), each with its own daily post/comment cap and minimum gap between actions (8h → 30min as tier rises) - Blended reputation score: verified provider data (when available) + this system's own observed track record (posts published, removal rate, approval rate) + a metadata-confidence weighting — unknown provider metadata increases caution but never permanently floors an otherwise-healthy account at Tier 0
- Rolling account health snapshots feeding tier decisions from real post/comment outcomes over time
- A hard minimum-gap enforcement across an account's entire history, shared across every campaign using that account — two campaigns on the same Reddit login compete for the same rate-limit budget, by design
Content-level
- Community Fit Reviewer ("would a respected member of this subreddit naturally write this?") — naturalness, tone, rule violations, and a request-vs-share check that specifically catches pure recommendation-request drafts on subreddits whose real, fetched rules discourage that pattern
- Promotion Detector — flags and can rewrite overt self-promotional language
- Duplicate Check — blocks repeated or near-identical content against the account's own history
- Community Simulation — three personas (Moderator, Experienced Member, First-time Reader) react to the draft before it's ever scheduled
- Subreddit rule honesty — curated subreddits get real, fetched rules interpreted by an LLM that's instructed to never invent a rule it wasn't given; LLM-suggested subreddits outside that dataset are explicitly flagged
rules_verified: falsewith no fabricated rule fields
Learning-level
- Verification loop checks every sent item back at +30 min / +24 h / +72 h for removal/engagement
- Reflection Agent folds real outcomes into that subreddit's Community DNA (
avoid_patterns/successful_patterns) — never assumed, always evidence-based - Voice Memory learns the account's own natural writing rhythm over time from its own successful posts, supplied to generation as a style reference only (never copied verbatim)
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # fill in DATABASE_URL, SECRET_KEY, an LLM API key, etc.
# Backend
uvicorn backend.main:app --reload --port 8001
# Frontend (separate shell)
streamlit run app.pySee .env.example for every configuration variable and what it's for — notably REDDIT_PROVIDER (zernio/oauth/mock/browser) and LLM_PROVIDER (google/openrouter).
frontend/ contains the primary UI: a React 19 + Vite + Tailwind v4 SPA
(react-router, TanStack Query, framer-motion). It covers the full product:
dark marketing landing + auth, dashboard with live system health, the
five-step campaign wizard (brief -> setup/connect -> subreddit discovery ->
content review with Trust Decisions -> schedule & launch), campaign detail
(upcoming / sent / activity), and logs.
cd frontend
npm install
npm run dev # http://localhost:5173, proxies /api -> :8001
npm run build # production bundle in frontend/distThe dev server proxies /api/* to the backend (set BACKEND_URL to override).
For a separately hosted build, point VITE_BACKEND_URL at the API and set
CORS_ORIGINS on the backend. The Streamlit UI below still works and is kept
for reference.
python -m pytest tests/ -qDeployed on Railway as two services sharing one Postgres plugin — no Dockerfile needed, Railway's Railpack builder detects Python from requirements.txt.
- Create a project and add a Postgres plugin — Railway gives it a
DATABASE_URLautomatically. - Add two services from this repo (or via
railway up --service <name>from the CLI), named e.g.backendandfrontend. - Set each service's start command (Settings → Deploy → Custom Start Command):
backend:uvicorn backend.main:app --host 0.0.0.0 --port $PORTfrontend:streamlit run app.py --server.port $PORT --server.address 0.0.0.0
- Environment variables, set per service:
backendneeds everything in.env.example— forDATABASE_URL, reference the Postgres plugin directly (${{Postgres.DATABASE_URL}}) so it always resolves to the live connection string.frontendonly needsBACKEND_URL— set it to thebackendservice's public Railway domain (e.g.https://backend-production-xxxx.up.railway.app), not localhost.
- Generate a public domain for
frontend(Settings → Networking) — this is the one link users need;backenddoesn't need a public domain iffrontendreaches it over Railway's private network, but a public one makes debugging withcurleasier. - Push to this repo (or
railway up --service <name>per service) to deploy. Tables are created automatically on backend startup viainit_db()— no manual migration step for a fresh database.
CLI equivalent for redeploying either service without a git push:
railway up --service backend
railway up --service frontendBuilt two end-to-end AI agent pipelines that turn a product description into personalized outreach — Instagram creator DMs and Reddit community posts — cutting manual prospecting and drafting from hours to minutes per campaign. ◦ Designed the full system solo: LLM-based lead discovery and message generation, an automated multi-stage review layer, and a persistent backend scheduler for reliable unattended delivery, backed by 164 automated tests. ◦ Integrated third-party posting infrastructure (Unipile, Zernio) for cross-platform delivery, with a reputation-based, rate-aware sending policy to keep automated accounts within normal usage patterns