Open-Source Cold Email & Warmup Platform
Warmbly is the open-source platform for cold email: warm your mailboxes, send campaigns, and triage every reply. Run it on our cloud or self-host the whole thing.
Upload your customer data and GrowthOS finds where revenue is leaking, writes the campaign to recover it, and tracks what converted. You approve the decisions — it does the work.
MarketingTypeScript · PLpgSQLGitHub @MithurnOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Live: https://growos-ai.vercel.app — open the demo with no sign-up. When the AWS backend is switched on, upload your own CSV and the agent runs in ~60 seconds. See DEPLOYMENT.md for how the always-on demo and the on-demand backend fit together.
Traditional CRMs make the marketer do all the thinking: build a segment, write a message, pick a channel, schedule a send. GrowthOS inverts this.
A scheduled growth agent prepares the work while the marketer keeps execution control:
The marketer's job shifts from building to steering.
Three independently deployable services:
┌─────────────────────────────────────────────────────────────┐
│ Frontend — Next.js App Router (Vercel) │
│ Onboarding · Dashboard · Opportunities · Campaigns · Analytics │
└────────────────────────┬────────────────────────────────────┘
│ REST + Auth JWT
┌────────────────────────▼────────────────────────────────────┐
│ Backend API — Express.js (Render) │
│ Ingestion · RFM Engine · AI Orchestrator · Campaign API │
│ requireAuth + resolveCompanyMiddleware on every route │
└──────────┬──────────────────────────────┬───────────────────┘
│ │ PostgreSQL + RLS
┌──────────▼──────────┐ ┌─────────────▼───────────────────┐
│ BullMQ Worker │────▶│ Channel Service │
│ Recipient outbox │ │ Twilio / Resend / Simulator │
└──────────┬──────────┘ └─────────────┬───────────────────┘
│ Redis │ HMAC callbacks
└───────────────────────────────┘
graph TD
subgraph CLIENT["Frontend — Next.js (Vercel)"]
ONB[Onboarding Wizard]
OPP[Opportunities Page]
CAM[Campaigns Page]
ANA[Analytics Page]
end
subgraph BACKEND["Backend API — Express.js (Render)"]
ING[Data Ingestion + RFM Engine]
PER[AI Persona Engine]
OPP_SVC[AI Opportunity Engine]
CAM_SVC[AI Campaign Generator]
LAUNCH[Campaign Launcher]
WH[Webhook Receiver]
end
subgraph CHANNEL["Channel Service — Node.js (Render)"]
QUEUE[Message Queue]
PROV[Twilio / Resend / Simulator]
CB[HMAC Webhook Emitter]
end
subgraph DB["Supabase — PostgreSQL (RLS on all tables)"]
T1[(customers · orders · products)]
T2[(personas · opportunities)]
T3[(campaigns · communications · events)]
end
CSV[CSV Upload] -->|multipart| ONB
ONB -->|POST /api/process-ingestion| ING
ING --> T1
T1 --> PER
PER -->|LLM| T2
T2 --> OPP_SVC
OPP_SVC -->|LLM| T2
T2 --> OPP
OPP --> CAM
CAM -->|LLM| CAM_SVC
CAM_SVC --> T3
CAM -->|launch| LAUNCH
LAUNCH --> T3
LAUNCH -->|transactional recipient outbox| QUEUE
QUEUE --> PROV
PROV --> CB
CB -->|POST /webhooks/channel-status| WH
WH --> T3
T3 --> ANA
requireAuth middleware on every Express route — rejects requests without a valid JWTresolveCompanyMiddleware resolves companyId exclusively from the authenticated user's DB profile — never trusted from the client requestPersisted AI generation uses a prompt → JSON parse → schema validate → DB write contract. Analytics uses deterministic aggregation and a safe fallback when an optional narrative call fails.
| Step | Schema enforced | Fallback |
|---|---|---|
| Persona Engine | { persona_name, description, reasoning } | Skip assignment, log |
| Opportunity Engine | Full typed opportunity object with numeric revenue fields | Discard, continue |
| Campaign Generator | { name, message_content, channel, objective, offer } | Return error to frontend |
QUEUED → SENT → DELIVERED → READ → CLICKED / FAILEDprocessed_webhook_events dedup table prevents duplicate processingsequenceNumber ensures status only advances forwardPromise.all, not sequentiallymain (root directory: frontend) plus the Actions backuprender.yaml links both services to Mithurn/GROWTH-OS main, deploys only after CI checks pass, and runs prisma migrate deploy before the backend starts| Concern | What I did | What I'd do at scale |
|---|---|---|
| Message delivery | Simulator fallback when no provider keys set; Twilio/Resend wired and ready | Add provider keys; no code changes needed |
| Job queue | BullMQ backed by required Redis; a separate worker entrypoint is ready, while the free-tier manifest runs workers in the API process | Enable the dedicated worker service before production traffic |
| Analytics realtime | 5s polling on campaign analytics page | Supabase Realtime subscriptions |
| Webhook ingestion | Transactional Prisma write with deduplication | Stream events when measured volume requires it |
| Cold starts | Health ping pre-warms the Render API | Paid always-on API and worker instances |
| Delivery guarantee | Durable at-least-once jobs; Resend receives a provider idempotency key | Add a provider-side dedup key for Twilio if its API supports one |
companies
└── profiles (user → company mapping, enforces multi-tenancy)
└── customers (external_id, RFM scores, persona assignment)
└── orders → order_items → products
└── personas (AI-generated behavioural archetypes)
└── opportunities (AI-detected, linked to persona distribution)
└── opportunity_customers (audience join table)
└── campaigns (AI-generated copy, channel, status)
└── communications (one row per recipient, status tracked)
└── communication_events (full delivery timeline)
└── processed_webhook_events (idempotency dedup)
└── agents (goal, guardrails, involvement level)
└── agent_actions (activity log — powers the live feed)
└── ingestion_sessions (upload progress tracking)
xeno-grow/
├── frontend/ # Next.js App Router · Tailwind · TypeScript
│ ├── app/
│ │ ├── page.tsx # Dashboard — opportunities + live agent activity
│ │ ├── onboarding/ # CSV upload + ingestion progress wizard
│ │ ├── opportunities/ # AI opportunity browser + goal input
│ │ ├── campaigns/ # Campaign builder + channel refiner
│ │ ├── analytics/ # Live campaign funnel + delivery stats
│ │ ├── personas/ # Customer persona breakdown
│ │ └── intelligence/ # AI intelligence brief
│ ├── lib/
│ │ ├── api.ts # All API calls + SWR cache layer
│ │ └── supabase/ # Auth client + token helpers
│ └── components/
│
├── backend/ # Express.js · Prisma · PostgreSQL · BullMQ · LangGraph
│ ├── src/
│ │ ├── server.ts # API process
│ │ ├── worker.ts # BullMQ worker process
│ │ ├── routes/ # Authenticated HTTP boundaries
│ │ ├── middleware/auth.ts # JWT verification + company resolution
│ │ ├── services/
│ │ │ ├── customer-attributes.ts # RFM + attribute engine
│ │ │ ├── personas.ts # AI persona generation
│ │ │ ├── opportunity-discovery.ts # AI opportunity engine
│ │ │ ├── campaigns.ts # Campaign CRUD + launch + state machine
│ │ │ ├── agent-logger.ts # Activity feed writes
│ │ │ └── agent-orchestrator.ts # Scheduled, overlap-safe agent runs
│ │ └── __tests__/ # Unit and real PostgreSQL integration suites
│ └── prisma/schema.prisma
│
├── channel-service/ # Standalone delivery service · Node.js
│ └── src/
│ ├── server.ts # POST /send endpoint
│ ├── queue.ts # Async message processor
│ ├── webhook.ts # HMAC-signed callback emitter
│ └── providers/
│ ├── twilio.ts # WhatsApp + SMS
│ ├── resend.ts # Email
│ └── simulator.ts # Fallback — QUEUED→SENT→DELIVERED→READ progression
│
├── .github/workflows/ci.yml # Typecheck + test pipeline (3 parallel jobs)
├── render.yaml # Declarative Render deployment config
└── scripts/start-all.sh # Local dev: starts all 3 services
Prerequisites: Node.js 22+, PostgreSQL with pgvector, Redis, Supabase Auth, and an OpenRouter API key
git clone https://github.com/Mithurn/xeno-grow
cd xeno-grow
chmod +x scripts/start-all.sh && ./scripts/start-all.sh
| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend API | http://localhost:3001 |
| Channel Service | http://localhost:5001 |
backend/.env
NEXT_PUBLIC_SUPABASE_URL=
SUPABASE_ANON_KEY=
DATABASE_URL=
OPENROUTER_API_KEY=
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_MODEL=openrouter/free
CHANNEL_SERVICE_URL=http://localhost:5001
WEBHOOK_SECRET= # openssl rand -base64 32
FRONTEND_URL=http://localhost:3000
channel-service/.env
CRM_WEBHOOK_URL=http://localhost:3001/api/webhooks/channel-status
WEBHOOK_SECRET= # must match backend exactly
FAILURE_RATE=10 # % of simulated sends that fail
# Optional — real sends when set:
# RESEND_API_KEY=
# TWILIO_ACCOUNT_SID=
# TWILIO_AUTH_TOKEN=
# TWILIO_PHONE_NUMBER=
# TWILIO_WHATSAPP_NUMBER=
frontend/.env.local
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
NEXT_PUBLIC_API_URL=http://localhost:3001/api
cd backend
TOTAL_CUSTOMERS=500 TOTAL_ORDERS=3000 npm run generate:data
# Outputs: backend/generated-data/customers.csv + orders.csv
# Upload both via the onboarding wizard at http://localhost:3000/onboarding
The agent will process your data and surface opportunities in ~60 seconds.
./scripts/verify-local.sh
This applies local migrations, regenerates Prisma, runs backend unit and
Testcontainers integration tests, then lints/builds the frontend and channel
service. If Docker is unavailable, use SKIP_INTEGRATION=1 to run everything
else. Remote databases are refused unless ALLOW_REMOTE_DB=1 and
CONFIRM_REMOTE_DB=I_UNDERSTAND are explicitly set.
./scripts/dev-local.sh
Open http://localhost:3000. Press Ctrl-C to stop all services.