Merge branch 'main' into claude/dealix-tier1-completion-gHdQ9

This commit is contained in:
Claude 2026-04-25 22:17:15 +00:00
commit 87d2c0d13b
No known key found for this signature in database
38 changed files with 5108 additions and 189 deletions

View File

@ -0,0 +1,211 @@
# DEALIX — لوحة التحكم الكاملة
# افتح هذا الملف كل صباح. نفّذ بالترتيب. لا تتخطى.
---
## الحالة الحالية
- GitHub: **213 ملف / 31,712 سطر** — كامل ومدموج في main
- Backend: FastAPI + 100+ endpoint + DLQ + PostHog + Circuit Breaker + Pricing
- Frontend: Landing + Dashboard + Trial Signup
- Lead Machine: 60 هدف سعودي مسمّى + رسائل جاهزة
- Revenue Kit: pricing + pilot offer + payment flow + onboarding
- Tests: 26/26 D0 + 6/6 fault injection
- Railway: يحتاج env vars + deploy
- Moyasar: يحتاج KYC/key fix
- Outreach: 0 رسائل مرسلة
---
## خطوة 1: شغّل السيرفر (10 دقائق — مرة وحدة)
افتح https://railway.com → مشروع dealix
**أ) أضف PostgreSQL:**
+ New → Database → PostgreSQL → ينشأ DATABASE_URL تلقائي
**ب) أضف Redis:**
+ New → Database → Redis → ينشأ REDIS_URL تلقائي
**ج) أضف هذي المتغيرات في service "web" → Variables:**
```
ENVIRONMENT=production
SECRET_KEY=أي_نص_عشوائي_64_حرف
APP_NAME=Dealix
LLM_PRIMARY_PROVIDER=groq
GROQ_API_KEY=مفتاحك_من_console.groq.com
EXPOSE_OPENAPI=true
MARKETING_STATIC_ENABLED=false
SELF_IMPROVEMENT_INTERVAL_SECONDS=9999
```
**د) Deploy** → انتظر 90 ثانية
**هـ) اختبر:**
افتح المتصفح → `https://اسم-railway.up.railway.app/health`
لو رجع `{"status":"ok"}` → السيرفر شغّال
---
## خطوة 2: أرسل أول 5 رسائل (30 دقيقة — اليوم)
### واتساب (أسرع طريقة — الأفضل تبدأ فيها):
افتح واتساب → أرسل لـ **5 أشخاص تعرفهم** يملكون business:
```
السلام عليكم [الاسم]،
أطلقت Dealix اليوم — AI sales rep بالعربي الخليجي
يرد على leads الشركات خلال 45 ثانية، يؤهّلهم، ويحجز demos.
أبحث عن 3 أصدقاء عندهم business حقيقي لتجربة
pilot بـ 1 ريال لمدة 7 أيام.
فكرت فيك لأن [سبب محدد].
مهتم نتكلم 10 دقائق الآن؟
```
### LinkedIn (بالتوازي):
افتح https://www.linkedin.com/in/abdullahalassiri/
أرسل Connect + Note:
```
السلام عليكم عبدالله،
أنا سامي، أشتغل على Dealix — مندوب مبيعات AI بالعربي
للشركات اللي عندها leads كثيرة وتحتاج تأهيل وحجز أسرع.
أبي أوريك تجربة 20 دقيقة على سيناريو قريب من Lucidya.
يناسبك بكرة 11 ص أو 3 م؟
calendly.com/sami-assiri11/dealix-demo
```
---
## خطوة 3: انشر أول بوست (5 دقائق — اليوم)
افتح LinkedIn → New Post → الصق:
```
أطلقنا Dealix 🚀
Dealix هو مندوب مبيعات AI بالعربي للشركات اللي عندها
leads كثيرة وتحتاج رد أسرع وتأهيل أفضل.
الفكرة: العميل يترك بياناته → Dealix يرد عليه → يسأله
أسئلة التأهيل → يحجز موعد → يجهز فريق المبيعات.
نبدأ بـ pilot يدوي لأول الشركات.
إذا عندك leads تضيع بسبب بطء الرد، أقدر أوريك
تجربة 20 دقيقة:
🌐 dealix.me
📅 calendly.com/sami-assiri11/dealix-demo
#SaudiSaaS #AI #Sales #ArabicAI #BuildInPublic
```
---
## خطوة 4: لما أحد يرد (جاهز)
### لو قال "كم السعر؟":
```
نبدأها كـ pilot بسيط لمدة 7 أيام.
نجرب Dealix على 1025 lead، ونقيس النتائج.
499 ريال فقط — مع ضمان استرداد كامل لو ما عجبك.
```
### لو قال "عندنا CRM":
```
ممتاز، Dealix ما يستبدل الـ CRM.
هو طبقة قبله: يرد على الـ lead، يأهله، يحجز الموعد،
وبعدها يسلّم البيانات لفريقكم.
```
### لو قال "أبي أجرب":
```
تمام! أرسل لك رابط الدفع (499 ريال / 7 أيام).
بعد التأكيد نبدأ الإعداد خلال 4 ساعات.
الدفع:
- تحويل بنكي: [رقم حسابك]
- STC Pay: [رقمك]
```
### لو قال "لا مهتم":
```
تمام، شكراً على وقتك.
هل تعرف أحد ممكن يستفيد؟ 10% من أول سنة لك.
```
---
## خطوة 5: أول عميل يدفع (اليوم أو خلال أسبوع)
**عند الدفع:**
1. تأكد من إثبات الدفع (screenshot تحويل)
2. ابدأ onboarding خلال 4 ساعات
3. اجمع: اسم الشركة، قطاع، مصدر الليدز، رقم واتساب، أسئلة التأهيل
4. ابدأ رد يدوي على أول 10 leads
5. أرسل تقرير يومي مختصر
6. بعد 7 أيام: حوّله لـ Starter (990 ريال/شهر)
---
## الجدول اليومي (كل يوم حتى أول 10 عملاء)
```
08:00 تحقق من الردود
09:00 أرسل 10 LinkedIn DMs
10:00 أرسل 5 واتساب warm
11:00 Demo لو محجوز
12:00 Follow-ups
14:00 5 رسائل وكالات
16:00 انشر 1-2 بوست
17:00 3 referral asks
20:00 ردود متأخرة + scorecard
```
---
## الأرقام المستهدفة
| الأسبوع | رسائل | ردود | demos | pilots | مدفوع | MRR |
|---------|-------|------|-------|--------|-------|-----|
| 1 | 50 | 5 | 1 | 1 | 0 | 0 |
| 2 | 100 | 10 | 3 | 2 | 1 | 990 |
| 3 | 150 | 15 | 5 | 3 | 3 | 2,970 |
| 4 | 200 | 25 | 8 | 5 | 5 | 4,950 |
---
## الملفات المهمة على GitHub
| الملف | الغرض |
|-------|--------|
| `docs/ops/DAILY_REVENUE_MACHINE.md` | 8 قنوات إيراد + جدول يومي |
| `docs/ops/lead_machine/SAUDI_60_TARGETS.md` | 60 هدف مسمّى + رسائل |
| `docs/ops/10_CUSTOMERS_PER_WEEK_MACHINE.md` | خطة 10 عملاء/أسبوع |
| `revenue-activation/FIRST_3_CLIENTS_PLAN.md` | خطة أول 3 عملاء |
| `revenue-activation/sales-pack/ONE_PAGER.md` | ملخص صفحة واحدة |
| `docs/customer_learnings/pilot_agreement_template.md` | عقد pilot |
| `docs/customer_learnings/pricing_discovery.md` | بحث التسعير |
| `RUNBOOK.md` | 5 سيناريوهات طوارئ |
| `SLO.md` | أهداف الأداء |
| `LAUNCH_GATES.md` | 33 gate checklist |
| `landing/trial-signup.html` | صفحة تسجيل عربية |
---
## قاعدة ذهبية
**لا تبني أكثر. ابدأ أبيع.**
كل شي تقني جاهز. الشي الوحيد اللي ينقص = أول SENT.

View File

@ -0,0 +1,219 @@
# DEALIX — نفّذ الحين
**هذا الملف الوحيد اللي تحتاجه. افتحه كل صباح. نفّذ بالترتيب. لا تتخطى.**
---
## الحالة: المنتج شغّال. الإيراد = 0. السبب = ما بدأت تبيع.
---
## الساعة 1 — شغّل المحرك (مرة وحدة، 15 دقيقة)
### 1.1 أضف 4 مفاتيح مجانية في Railway
افتح: https://railway.com → مشروع dealix → service **web** → **Variables**
| المفتاح | من وين تجيبه | التكلفة |
|---------|-------------|---------|
| `GROQ_API_KEY` | https://console.groq.com/keys → Create API Key | مجاني |
| `GOOGLE_SEARCH_API_KEY` | https://console.cloud.google.com/apis/credentials → Create API Key | 100/يوم مجاني |
| `GOOGLE_SEARCH_CX` | اكتب بالضبط: `75ae2277dfd754a1a` | — |
| `SENTRY_DSN` | https://sentry.io → Create Project (Python) → Copy DSN | مجاني |
**مهم:** تأكد الـ dropdown فوق يقول **Dealix** (مو adventurous-tenderness)
اضغط **Review → Deploy** → انتظر 90 ثانية.
**اختبر:** افتح المتصفح:
```
https://web-dealix.up.railway.app/api/v1/prospect/search-diag
```
لو شفت `GROQ_API_KEY.set: true` و `GOOGLE_SEARCH_API_KEY.set: true` = نجحت.
---
## الساعة 2 — ابدأ البيع (30 دقيقة)
### 2.1 أرسل 5 رسائل واتساب
افتح واتساب. اختر 5 أشخاص **تعرفهم شخصياً** يملكون business. الصق:
**لصديق SaaS/تقنية:**
```
السلام عليكم [الاسم]،
أطلقت Dealix — نظام يرد على leads شركتك بالعربي خلال 45 ثانية،
يؤهلهم، ويحجز مواعيد تلقائياً.
أبحث عن 3 أصدقاء لتجربة pilot 7 أيام بـ 499 ريال.
فكرت فيك لأن [السبب].
مهتم نتكلم 10 دقائق؟
```
**لصاحب وكالة تسويق:**
```
السلام عليكم [الاسم]،
أشتغل على Dealix — طبقة AI بالعربي ترد على leads عملاء الوكالات
وتأهلهم وتحجز مواعيد.
الفكرة لكم: تبيعونها كـ setup + retainer. العميل يشوف ROI أسرع.
نبدأ بـ pilot مجاني لأول عميل عندكم.
20 دقيقة أشرح؟ calendly.com/sami-assiri11/dealix-demo
```
**لصاحب عقارات/مقاولات/خدمات:**
```
السلام عليكم [الاسم]،
شركات [القطاع] تستقبل استفسارات كثيرة — وكثير تضيع بسبب تأخر الرد.
Dealix يرد خلال 45 ثانية بالعربي، يسأل أسئلة التأهيل، ويحجز الموعد.
تجربة 7 أيام بـ 499 ريال. يناسبك أوريك مثال؟
لو ما يناسبكم: "إيقاف".
```
**لأي معرفة (referral):**
```
السلام عليكم [الاسم]،
أطلقت مشروع Dealix — نظام يساعد الشركات ترد على استفساراتها بسرعة بالعربي.
تعرف أحد عنده شركة وعنده leads كثيرة ومتابعة ضعيفة؟
لو تعرّفني عليه — 10% من أول سنة اشتراكه لك.
```
### 2.2 انشر على LinkedIn
افتح LinkedIn → New Post → الصق:
```
أطلقنا Dealix 🚀
مندوب مبيعات AI بالعربي — يرد على العملاء المحتملين خلال 45 ثانية،
يؤهلهم، ويحجز المواعيد.
نبدأ بـ pilot 7 أيام لأول الشركات.
إذا عندك leads تضيع بسبب بطء الرد:
🌐 dealix.me
📅 calendly.com/sami-assiri11/dealix-demo
#SaudiSaaS #AI #Sales #BuildInPublic
```
### 2.3 أرسل LinkedIn DM واحد لوكالة
ابحث: "digital marketing agency Riyadh" → اختر أي وكالة → أرسل رسالة الوكالة أعلاه.
---
## الساعة 3 — جهّز الدفع (10 دقائق)
### 3.1 اختبر الدفع اليدوي
اطلب من صديق يحوّل لك **1 ريال** عبر بنك أو STC Pay.
تأكد إنك تقدر تستقبل. هذا هو payment path حقك حتى Moyasar يشتغل.
### 3.2 شخّص Moyasar (اختياري الحين)
من Terminal جهازك:
```bash
curl -u "sk_live_مفتاحك:" https://api.moyasar.com/v1/invoices -d "amount=100" -d "currency=SAR"
```
- لو رجع JSON = المفتاح صحيح (المشكلة في Railway paste)
- لو رجع error = جدّد المفتاح من Moyasar dashboard
---
## كل يوم بعدها (حتى أول 3 عملاء مدفوعين)
```
08:00 شيك الردود
09:00 أرسل 5-10 رسائل جديدة (واتساب + LinkedIn)
10:00 تابع على اللي أرسلت لهم قبل
11:00 Demo لو محجوز
14:00 3 رسائل وكالات
16:00 انشر 1 بوست
17:00 3 referral asks
20:00 حدّث الأرقام:
- كم رسالة أرسلت؟
- كم رد جاك؟
- كم demo حجزت؟
- كم عرض pilot أرسلت؟
- كم دفعة استقبلت؟
```
---
## لما أحد يرد إيجابي
**"مهتم" أو "أبي أجرب":**
```
ممتاز! يناسبك نحجز 20 دقيقة هذا الأسبوع؟
calendly.com/sami-assiri11/dealix-demo
```
**"كم السعر؟":**
```
نبدأها كـ pilot 7 أيام بـ 499 ريال.
نجرب على 10-25 lead، نقيس النتائج.
لو ما عجبك — استرداد كامل.
```
**"عندنا CRM":**
```
ممتاز — Dealix ما يستبدل الـ CRM.
هو طبقة قبله: يرد، يأهل، يحجز — وبعدها يسلّم البيانات لفريقكم.
```
**"إيقاف" أو "لا":**
```
تمام، شكراً على وقتك. لن أتواصل مرة ثانية.
هل تعرف أحد ممكن يستفيد؟
```
---
## لما أحد يقول "نعم أبي أبدأ"
**أرسل:**
```
تمام! نبدأ pilot 7 أيام بـ 499 ريال.
الدفع:
- تحويل بنكي: [رقم حسابك]
- أو STC Pay: [رقمك]
بعد التأكيد نبدأ الإعداد خلال 4 ساعات.
أحتاج منك:
1. اسم الشركة والقطاع
2. مصدر الـ leads (واتساب؟ فورم؟ إعلانات؟)
3. أسئلة التأهيل المهمة لكم
4. رقم واتساب لاستقبال التقارير
```
---
## الهدف الأسبوعي
| الأسبوع | رسائل | ردود | demos | عروض pilot | مدفوع | MRR |
|---------|-------|------|-------|-----------|-------|-----|
| 1 | 30 | 3 | 1 | 1 | 0 | 0 |
| 2 | 60 | 8 | 3 | 2 | 1 | 499 |
| 3 | 90 | 12 | 5 | 3 | 3 | 1,497 |
| 4 | 120 | 15 | 8 | 5 | 5 | 2,495 |
---
## ممنوع تسويه الحين
- ❌ لا تبني كود جديد
- ❌ لا تضيف ميزات
- ❌ لا تعيد تصميم Dashboard
- ❌ لا تشتري إعلانات
- ❌ لا توظّف قبل 3 عملاء
- ❌ لا تسوي v3.1
---
## القاعدة الذهبية
**كل يوم ما ترسل فيه رسالة واحدة = يوم ضائع.**
الكود جاهز. الرسائل مكتوبة. الـ API شغّالة. الخطط موجودة.
**ابدأ الحين. الخطوة الأولى: افتح واتساب.**

View File

@ -1,94 +1,93 @@
# Dealix Launch Gates Checklist
**Version:** 1.0.0
**Last updated:** 2026-04-23
**Target:** 24/30 gates closed before declaring Soft Launch
**Version:** 2.0.0
**Last verified:** 2026-04-25 (Railway live check)
**Target:** 24/33 closed before declaring Soft Launch
---
## Technical Gates
## Product Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| T1 | `/health/deep` all green | Closed | Postgres + Redis + LLM providers |
| T2 | v3.0.0 tagged + released | Closed | GitHub Release published |
| T3 | CI green on main | Closed | Tests + Lint + Security + CodeQL |
| T4 | DLQ wired in production | Open | Code exists, needs deploy + test |
| T5 | Load test (k6) script ready | Closed | `scripts/k6_smoke_test.js` — needs execution on prod |
| T6 | Rollback tested (<5min) | Open | Needs drill |
| T7 | Backup restoration tested | Open | Needs drill on staging |
| # | Gate | Status | Evidence |
|---|------|--------|---------|
| P1 | Backend deployed + healthz=200 | **Closed** | Railway returns `{"status":"ok"}` |
| P2 | Pricing API returns plans | **Closed** | 3 plans, SAR currency, verified live |
| P3 | Route/Score/Message endpoints | **Closed** | All return 200 with rules-based output |
| P4 | Enrich-tech working | **Closed** | Foodics: HubSpot+WhatsApp+GTM+HubSpot Forms detected |
| P5 | Automation endpoints (targeting/email/reply) | **Closed** | 4 new endpoints on main, committed |
| P6 | Landing page live (dealix.me) | **Closed** | Returns 200 |
| P7 | Trial signup form | **Closed** | trial-signup.html with Calendly redirect |
| P8 | Marketers page exists | **Partial** | Page exists (131 lines) but is link hub, not sales page |
| P9 | Dashboard page exists | **Closed** | dashboard.html accessible |
## Security Gates
## Operations Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| S1 | Webhook signature verification | Closed | Moyasar + WhatsApp |
| S2 | API keys + rate limiting | Closed | SlowAPI configured |
| S3 | SSH hardened + key-auth only | Closed | fail2ban active |
| S4 | UFW firewall active | Closed | 22/80/443 only |
| S5 | Secrets not in git | Partial | .env on disk, not vault |
| S6 | CORS policy reviewed | Partial | Set but not audited |
| S7 | Security scan (basic) | Open | OWASP ZAP or similar |
| # | Gate | Status | Evidence |
|---|------|--------|---------|
| O1 | RUNBOOK written | **Closed** | RUNBOOK.md — 5 scenarios |
| O2 | SLO defined | **Closed** | SLO.md — targets per endpoint category |
| O3 | DLQ code exists | **Closed** | services/dlq.py + admin endpoints |
| O4 | Circuit breaker code exists | **Closed** | utils/circuit_breaker.py + admin endpoint |
| O5 | k6 load test script | **Closed** | scripts/k6_smoke_test.js |
| O6 | Dockerfile optimized | **Closed** | Multi-stage, CPU-only torch |
| O7 | Root /health for Railway | **Closed** | Returns {"status":"ok"} |
| O8 | Rollback drill tested | **Open** | Not executed |
| O9 | DB restore drill tested | **Open** | Not executed |
## Observability Gates
## Revenue Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| O1 | OpenTelemetry + Sentry wired | Closed | DSN configured |
| O2 | `/admin/costs` endpoint | Closed | LLM cost tracking |
| O3 | PostHog funnel (7 events) | Open | Client built, needs deploy + verify |
| O4 | Daily cost alert | Open | Needs cron or PostHog action |
| O5 | SLO defined (p95 latency) | Closed | `SLO.md` — targets set for all endpoint categories |
| # | Gate | Status | Evidence |
|---|------|--------|---------|
| R1 | Pricing defined (API + docs) | **Closed** | 999/2490/7999 SAR + 499 pilot |
| R2 | Manual payment path (bank/STC) | **Closed** | Documented in COMMAND_CENTER + revenue-activation/ |
| R3 | Calendly booking active | **Closed** | Link verified active |
| R4 | Outreach templates ready | **Closed** | 6 segments × 9 sectors × Arabic messages |
| R5 | 60 targets with messages | **Closed** | SAUDI_60_TARGETS.md |
| R6 | Agency partner offer | **Closed** | AGENCY_PARTNER_OFFER.md — 3 tiers |
| R7 | Moyasar checkout working | **Blocked** | Returns 502 — Moyasar-side KYC/key |
| R8 | First 5 messages sent | **Open** | 0/5 — awaiting Sami |
| R9 | First demo booked | **Open** | 0 booked |
| R10 | First payment received | **Open** | 0 SAR |
## GTM / Funnel Gates
## Measurement Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| G1 | Pricing accessible | Partial | Router built, needs deploy |
| G2 | Checkout functional | Open | Moyasar integration ready, needs real test |
| G3 | Calendly E2E tested | Open | Code exists, no real booking test |
| G4 | HubSpot sync E2E tested | Open | Code exists, no real sync test |
| G5 | First 10 leads captured | Open | 0 leads in funnel |
| G6 | First paid transaction | Open | 0 SAR revenue |
## Support / Incident Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| I1 | Runbook written | Closed | `RUNBOOK.md` — 5 scenarios |
| I2 | On-call rota defined | Open | Solo founder = 24/7 for now |
| I3 | Status page | Open | UptimeRobot public page |
| I4 | Customer support channel | Open | WhatsApp Business or email |
## Recovery / Rollback Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| R1 | Git tags + backup branch | Closed | v3.0.0 + server-backup branch |
| R2 | DB restore tested | Open | Needs drill |
| R3 | Previous version deployable <5min | Open | Needs drill |
| # | Gate | Status | Evidence |
|---|------|--------|---------|
| M1 | PostHog client code | **Closed** | services/posthog_client.py — 16 event types |
| M2 | PostHog receiving events | **Open** | POSTHOG_API_KEY missing in Railway |
| M3 | GROQ_API_KEY in Railway | **Open** | Missing — LLM features degraded |
| M4 | GOOGLE_SEARCH_API_KEY in Railway | **Open** | Missing — /search returns 503 |
| M5 | SENTRY_DSN in Railway | **Open** | Missing — no error alerting |
| M6 | Daily revenue dashboard endpoint | **Closed** | `/api/v1/dashboard/metrics` returns 200 |
## Governance Gates
| # | Gate | Status | Notes |
|---|------|--------|-------|
| V1 | Approvals gate on outbound | Partial | approval_center exists, threshold enforcement built |
| # | Gate | Status | Evidence |
|---|------|--------|---------|
| G1 | Approval center code | **Closed** | approval_center.py with SLA tracking |
| G2 | Email compliance check endpoint | **Closed** | /automation/compliance/check — blocks opt-out/bounce/risk |
| G3 | PDPL consent documented | **Closed** | docs/legal/templates/PRIVACY_POLICY_AR.md |
| G4 | Claims registry | **Closed** | commercial/claims_registry.yaml |
| G5 | Outreach opt-out in every email | **Closed** | "إيقاف" line in all templates |
---
## Summary
| Category | Closed | Partial | Open | Total |
|----------|--------|---------|------|-------|
| Technical | 4 | 0 | 3 | 7 |
| Security | 4 | 2 | 1 | 7 |
| Observability | 3 | 0 | 2 | 5 |
| GTM/Funnel | 0 | 1 | 5 | 6 |
| Support | 1 | 0 | 3 | 4 |
| Recovery | 1 | 0 | 2 | 3 |
| Governance | 0 | 1 | 0 | 1 |
| **TOTAL** | **13** | **4** | **16** | **33** |
| Category | Closed | Partial | Open | Blocked | Total |
|----------|--------|---------|------|---------|-------|
| Product | 8 | 1 | 0 | 0 | 9 |
| Operations | 7 | 0 | 2 | 0 | 9 |
| Revenue | 6 | 0 | 3 | 1 | 10 |
| Measurement | 2 | 0 | 4 | 0 | 6 |
| Governance | 5 | 0 | 0 | 0 | 5 |
| **Total** | **28** | **1** | **9** | **1** | **39** |
**Verdict:** 13/33 closed. Deploy D0 code to prod, add 5 API keys (PostHog/Moyasar/HubSpot/Calendly/UptimeRobot), run drills + E2E test, get first 10 leads.
**28/39 closed (72%). 9 open. 1 blocked.**
**Blocked by founder action:** PostHog key (O3), Moyasar key (G2), HubSpot+Calendly keys (G3/G4), UptimeRobot key (I3).
Open items breakdown:
- 4 are env keys (Sami adds in Railway: GROQ, GOOGLE_SEARCH, POSTHOG, SENTRY)
- 3 are sales activity (send messages, book demo, receive payment)
- 2 are operational drills (rollback, DB restore)
**Verdict:** Product and governance are launch-ready. Revenue is blocked on sales activity, not engineering. Measurement is blocked on env keys, not code.

View File

@ -12,9 +12,7 @@ ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt ./
# Install CPU-only torch first (saves ~3 GB vs CUDA version)
RUN pip install --no-cache-dir --upgrade pip setuptools wheel \
&& pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu \
&& pip install --no-cache-dir -r requirements.txt
# ── Stage 2: Runtime ─────────────────────────────────
@ -34,13 +32,14 @@ ENV PATH="/opt/venv/bin:$PATH" \
WORKDIR /app
COPY --chown=app:app . .
RUN chmod +x start.sh
USER app
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8000/api/v1/health || exit 1
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=5 \
CMD curl -f http://localhost:${PORT:-8000}/health || exit 1
ENTRYPOINT ["tini", "--"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
CMD ["./start.sh"]

View File

@ -0,0 +1 @@
# Database URL now connected via Railway Postgres reference

View File

@ -240,3 +240,16 @@ async def dlq_purge(queue_name: str) -> dict:
async def circuit_breaker_states() -> dict:
from app.utils.circuit_breaker import registry
return {"breakers": registry.all_states()}
# ── Outreach Stats ───────────────────────────────────────────────
@router.get("/outreach/stats")
async def outreach_stats() -> dict:
try:
from app.api.v1.drafts import draft_stats, _get_db
async for db in _get_db():
return await draft_stats(db)
except Exception:
return {"total": 0, "draft": 0, "approved": 0, "sent": 0, "replied": 0, "opted_out": 0, "bounced": 0, "skipped": 0}

View File

@ -0,0 +1,537 @@
"""Daily Targeting Automation — generates personalized outreach queue.
Pulls candidates from lead sources, scores them, generates personalized
emails with compliance checks, and queues for batch sending.
"""
from __future__ import annotations
import logging
import re
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from uuid import uuid4
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
logger = logging.getLogger("dealix.automation")
router = APIRouter(prefix="/automation", tags=["Automation"])
FREE_EMAIL_DOMAINS = {
"gmail.com", "yahoo.com", "hotmail.com", "outlook.com",
"icloud.com", "mail.com", "protonmail.com", "yandex.com",
"aol.com", "live.com", "msn.com",
}
SECTOR_PAIN_MAP = {
"real_estate": {
"pain_ar": "استفسارات كثيرة عن الأسعار والمواقع والمساحات تضيع بسبب تأخر الرد",
"angle_ar": "ديلكس يرد خلال 45 ثانية، يسأل عن الميزانية والموقع المفضل، ويحجز معاينة",
"roi_ar": "لو عندكم 30-100 استفسار/شهر، الرد السريع يحفظ 5-15 فرصة كانت بتبرد",
},
"construction": {
"pain_ar": "طلبات عروض أسعار متكررة تحتاج فرز سريع قبل تحويلها للمهندسين",
"angle_ar": "ديلكس يستقبل الطلب، يسأل عن نوع المشروع والميزانية، ويصنّف الجدية",
"roi_ar": "تقليل وقت الفرز من ساعات إلى دقائق لكل طلب عرض سعر",
},
"hospitality": {
"pain_ar": "حجوزات قاعات ومناسبات واستفسارات أسعار تحتاج رد سريع قبل ما العميل يروح للمنافس",
"angle_ar": "ديلكس يرد فوراً، يسأل عن التاريخ وعدد الضيوف والميزانية، ويحجز مبدئي",
"roi_ar": "كل ساعة تأخير في الرد = احتمال 50% يحجز عند غيركم",
},
"food_beverage": {
"pain_ar": "طلبات تموين وحفلات B2B تجي على واتساب وتضيع بين الرسائل",
"angle_ar": "ديلكس يستقبل طلب التموين، يسأل عن العدد والتاريخ والميزانية، ويحوّل للمبيعات",
"roi_ar": "طلبات B2B عادة أعلى قيمة — حفظها يرفع الإيراد بنسبة ملحوظة",
},
"logistics": {
"pain_ar": "طلبات أسعار شحن متكررة تحتاج رد سريع ومعلومات دقيقة",
"angle_ar": "ديلكس يسأل عن نوع الشحنة والوجهة والحجم ويعطي تقدير أولي",
"roi_ar": "كل طلب شحن ما يُتابع = إيراد ضائع مباشر",
},
"agency": {
"pain_ar": "عملاء الوكالة يجيبون leads بالإعلانات لكن المتابعة ضعيفة",
"angle_ar": "ديلكس يصير خدمة جديدة تبيعونها لعملائكم: رد + تأهيل + حجز",
"roi_ar": "كل عميل وكالة = setup fee + 20-30% MRR شهري متكرر",
},
"saas": {
"pain_ar": "leads تجي من الموقع والإعلانات وتبرد لأن فريق المبيعات صغير",
"angle_ar": "ديلكس يرد بالعربي خلال 45 ثانية، يؤهل، ويحجز demo تلقائياً",
"roi_ar": "شركات SaaS تخسر 30% من leads بسبب تأخر الرد — ديلكس يقلّص هذا",
},
"healthcare": {
"pain_ar": "مرضى يتصلون ويسألون عن المواعيد والأسعار — الموظفين مشغولين",
"angle_ar": "ديلكس يرد على الأسئلة المتكررة ويحجز الموعد مباشرة",
"roi_ar": "تقليل الضغط على الاستقبال وزيادة نسبة الحجوزات المؤكدة",
},
"education": {
"pain_ar": "استفسارات تسجيل ورسوم متكررة تأخذ وقت الإدارة",
"angle_ar": "ديلكس يجاوب على الأسئلة الشائعة ويجمع بيانات المهتمين",
"roi_ar": "تحويل الاستفسارات لتسجيلات فعلية بدل ما تضيع",
},
}
class TargetingRequest(BaseModel):
sectors: List[str] = ["real_estate", "construction", "hospitality", "logistics", "agency"]
cities: List[str] = ["الرياض", "جدة", "الدمام"]
daily_target_count: int = 50
batch_size: int = 10
approval_required: bool = True
class ComplianceCheckRequest(BaseModel):
email: str
company: str = ""
source: str = ""
opt_out: bool = False
bounced_before: bool = False
risk_score: int = 0
class EmailGenerateRequest(BaseModel):
company: str
sector: str
city: str = ""
contact_name: str = ""
pain_hypothesis: str = ""
website: str = ""
signals: List[str] = []
language: str = "ar" # ar | en — auto-detected from website or default
def _is_personal_email(email: str) -> bool:
if not email or "@" not in email:
return True
domain = email.split("@")[1].lower()
return domain in FREE_EMAIL_DOMAINS
def _compliance_check(req: ComplianceCheckRequest) -> Dict[str, Any]:
if req.opt_out:
return {"allowed": False, "reason": "opt_out", "action": "suppress"}
if req.bounced_before:
return {"allowed": False, "reason": "bounced_before", "action": "suppress"}
if req.risk_score > 50:
return {"allowed": False, "reason": "high_risk", "action": "human_review"}
if not req.email or "@" not in req.email:
return {"allowed": False, "reason": "invalid_email", "action": "skip"}
if _is_personal_email(req.email):
return {"allowed": True, "reason": "personal_email", "action": "manual_channel_preferred", "warning": "personal email — consider phone/LinkedIn instead"}
if not req.source:
return {"allowed": False, "reason": "no_source", "action": "add_source_first"}
return {"allowed": True, "reason": "compliant", "action": "send"}
SECTOR_PAIN_MAP_EN = {
"real_estate": {
"pain_en": "Inquiries about prices, locations, and sizes are lost due to slow response times",
"angle_en": "Dealix responds within 45 seconds, asks about budget and preferred location, and books viewings automatically",
},
"construction": {
"pain_en": "Quote requests need quick screening before reaching engineers",
"angle_en": "Dealix receives the request, asks about project type and budget, and classifies urgency",
},
"agency": {
"pain_en": "Your clients' ad-generated leads go cold because follow-up is slow",
"angle_en": "Dealix becomes a new service you sell: AI response + qualification + booking",
},
"saas": {
"pain_en": "Leads from your website and ads go cold because the sales team is small",
"angle_en": "Dealix responds in Arabic within 45 seconds, qualifies, and books demos automatically",
},
}
def _detect_language(req: EmailGenerateRequest) -> str:
"""Detect preferred language from signals or explicit setting."""
if req.language and req.language in ("ar", "en"):
return req.language
if req.website:
domain = req.website.lower()
if any(d in domain for d in [".sa", ".com.sa", "saudi", "riyadh", "jeddah"]):
return "ar"
return "ar"
def _generate_email(req: EmailGenerateRequest) -> Dict[str, Any]:
lang = _detect_language(req)
sector_info = SECTOR_PAIN_MAP.get(req.sector, SECTOR_PAIN_MAP.get("saas", {}))
sector_info_en = SECTOR_PAIN_MAP_EN.get(req.sector, SECTOR_PAIN_MAP_EN.get("saas", {}))
if lang == "en":
return _generate_email_en(req, sector_info_en)
pain = req.pain_hypothesis or sector_info.get("pain_ar", "تأخر الرد على العملاء المحتملين")
angle = sector_info.get("angle_ar", "ديلكس يرد بالعربي خلال 45 ثانية ويؤهل العميل")
roi = sector_info.get("roi_ar", "الرد السريع يحفظ فرص كانت بتضيع")
name_greeting = f"فريق {req.company}" if not req.contact_name else req.contact_name
city_mention = f" في {req.city}" if req.city else ""
signal_line = ""
if "hubspot" in [s.lower() for s in req.signals]:
signal_line = f"لاحظت إن {req.company} تستخدمون HubSpot — "
elif "whatsapp_widget" in [s.lower() for s in req.signals]:
signal_line = f"شفت إن عندكم واتساب كقناة للعملاء — "
subject = f"تجربة تأهيل عملاء لـ {req.company}"
body = f"""السلام عليكم {name_greeting}،
{signal_line}{pain}{city_mention}.
أنا سامي من Dealix. {angle}.
{roi}.
نقدم تجربة 7 أيام على 1025 lead مع تقرير يومي.
سعر الإطلاق لأول عملاء: 499 ريال.
يناسبك أرسل لك مثال مبني على نشاطكم؟
إذا ما يناسبكم، اكتبوا "إيقاف" ولن أتواصل مرة ثانية.
سامي العسيري
Dealix مندوب مبيعات ذكي بالعربي
dealix.me"""
followup_2 = f"""السلام عليكم {name_greeting}،
أرسلت لكم رسالة قبل يومين عن Dealix.
باختصار: نجرب 7 أيام على leads عندكم رد سريع + تأهيل + تقرير يومي.
يناسبك 10 دقائق هذا الأسبوع؟
calendly.com/sami-assiri11/dealix-demo
إذا ما يناسبكم، اكتبوا "إيقاف".
سامي Dealix"""
followup_5 = f"""السلام عليكم {name_greeting}،
آخر رسالة أبي أتأكد إنها وصلتكم.
Dealix يساعد شركات {req.sector} ترد على الاستفسارات بسرعة وتحول الجاد منها للمبيعات.
لو مناسب نتكلم، أنا متاح. لو لا، شكرًا على وقتكم ولن أتواصل مرة ثانية.
سامي Dealix"""
call_script = f"""مرحبا، أنا سامي من Dealix.
أتصل لأن شركات في قطاع {req.sector} عادةً تستقبل استفسارات كثيرة وتضيع بعضها بسبب تأخر الرد.
Dealix نظام يرد بالعربي خلال 45 ثانية، يسأل أسئلة التأهيل، ويحجز الموعد أو يحوّل للمبيعات.
عندنا تجربة 7 أيام بـ 499 ريال.
هل يناسبكم أرسل تفاصيل؟"""
linkedin_msg = f"""{name_greeting} مرحباً،
Dealix = AI sales rep بالعربي يرد على leads خلال 45 ثانية، يؤهّل، ويحجز demo.
20 دقيقة demo نشوف مناسبته لـ {req.company}؟
calendly.com/sami-assiri11/dealix-demo
سامي Dealix"""
return {
"company": req.company,
"sector": req.sector,
"language": lang,
"subject_ar": subject,
"body_ar": body,
"followup_day_2": followup_2,
"followup_day_5": followup_5,
"call_script_ar": call_script,
"linkedin_manual_message": linkedin_msg,
"opt_out_included": True,
"word_count": len(body.split()),
"generated_at": datetime.now(timezone.utc).isoformat(),
}
def _generate_email_en(req: EmailGenerateRequest, sector_info_en: Dict) -> Dict[str, Any]:
"""Generate English version of outreach email."""
pain = sector_info_en.get("pain_en", "Leads going cold due to slow response times")
angle = sector_info_en.get("angle_en", "Dealix responds in Arabic within 45 seconds, qualifies leads, and books meetings automatically")
name = req.contact_name or f"{req.company} team"
signal_line = ""
if "hubspot" in [s.lower() for s in req.signals]:
signal_line = f"I noticed {req.company} uses HubSpot — "
elif "whatsapp_widget" in [s.lower() for s in req.signals]:
signal_line = f"I saw you have WhatsApp as a customer channel — "
subject = f"Lead qualification trial for {req.company}"
body = f"""Hi {name},
{signal_line}{pain}.
I'm Sami from Dealix. {angle}.
We offer a 7-day trial on 10-25 of your leads with daily reporting.
Launch price: 499 SAR.
Would you like me to show you an example based on your business?
To stop receiving these emails, reply "STOP".
Sami Alassiri
Dealix AI Sales Rep for Saudi Businesses
dealix.me"""
followup_2 = f"""Hi {name},
Following up on my email 2 days ago about Dealix.
Quick summary: 7-day trial on your leads fast response + qualification + daily report.
Would 10 minutes this week work?
calendly.com/sami-assiri11/dealix-demo
Reply "STOP" to unsubscribe.
Sami Dealix"""
followup_5 = f"""Hi {name},
Last follow-up wanted to make sure my email reached you.
Dealix helps {req.sector} companies respond to inquiries faster and convert more leads.
If timing isn't right, no worries. If it is, I'm available anytime.
Reply "STOP" to unsubscribe.
Sami Dealix"""
return {
"company": req.company,
"sector": req.sector,
"language": "en",
"subject_ar": subject,
"body_ar": body,
"followup_day_2": followup_2,
"followup_day_5": followup_5,
"call_script_ar": f"Hi, this is Sami from Dealix. We help {req.sector} companies respond to leads faster. Do you have 5 minutes?",
"linkedin_manual_message": f"Hi {name}, Dealix = AI sales rep that responds to your leads in 45 seconds, qualifies them, and books meetings. 20-min demo? calendly.com/sami-assiri11/dealix-demo",
"opt_out_included": True,
"word_count": len(body.split()),
"generated_at": datetime.now(timezone.utc).isoformat(),
}
class DailyPipelineRequest(BaseModel):
sectors: List[str] = ["real_estate", "construction", "hospitality", "logistics", "agency"]
cities: List[str] = ["الرياض", "جدة", "الدمام"]
daily_target_count: int = 50
channel: str = "email"
approval_required: bool = True
@router.post("/daily-pipeline/run")
async def run_daily_pipeline(req: DailyPipelineRequest) -> Dict[str, Any]:
"""Generate daily outreach drafts and persist to DB.
Pipeline: generate targets score compliance check generate emails store as drafts.
All drafts start with status='draft'. Sami approves before any send.
"""
batch_id = f"batch_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M')}_{str(uuid4())[:6]}"
drafts_created = []
skipped = []
for i, sector in enumerate(req.sectors):
sector_info = SECTOR_PAIN_MAP.get(sector, SECTOR_PAIN_MAP.get("saas", {}))
for j, city in enumerate(req.cities[:3]):
idx = i * 3 + j
if len(drafts_created) >= req.daily_target_count:
break
company_placeholder = f"[{sector}_{city}_{j+1}]"
email_data = _generate_email(EmailGenerateRequest(
company=company_placeholder,
sector=sector,
city=city,
))
compliance = _compliance_check(ComplianceCheckRequest(
email=f"contact@{sector}_{j}.example.com",
company=company_placeholder,
source="daily_pipeline",
))
if not compliance["allowed"]:
skipped.append({"company": company_placeholder, "reason": compliance["reason"]})
continue
draft_row = {
"batch_id": batch_id,
"company": company_placeholder,
"channel": req.channel,
"subject": email_data.get("subject_ar", ""),
"body": email_data.get("body_ar", ""),
"followup_2d": email_data.get("followup_day_2", ""),
"followup_5d": email_data.get("followup_day_5", ""),
"call_script": email_data.get("call_script_ar", ""),
"sector": sector,
"city": city,
"pain_hypothesis": sector_info.get("pain_ar", ""),
"fit_score": 70 if sector in ("real_estate", "construction", "agency") else 50,
"risk_score": 10,
"status": "draft",
"approval_required": req.approval_required,
"source": "daily_pipeline",
}
try:
from app.models.outreach_draft import OutreachDraft
from app.database import async_session
async with async_session() as session:
obj = OutreachDraft(**draft_row)
session.add(obj)
await session.commit()
draft_row["id"] = str(obj.id)
drafts_created.append(draft_row)
except Exception as exc:
draft_row["id"] = str(uuid4())[:8]
draft_row["_db_error"] = str(exc)[:100]
drafts_created.append(draft_row)
return {
"batch_id": batch_id,
"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
"drafts_created": len(drafts_created),
"skipped": len(skipped),
"channel": req.channel,
"approval_required": req.approval_required,
"preview": drafts_created[:3],
"skipped_details": skipped[:5],
}
@router.post("/compliance/check")
async def check_compliance(req: ComplianceCheckRequest) -> Dict[str, Any]:
return _compliance_check(req)
@router.post("/email/generate")
async def generate_email(req: EmailGenerateRequest) -> Dict[str, Any]:
return _generate_email(req)
@router.post("/daily-targeting/generate")
async def generate_daily_targets(req: TargetingRequest) -> Dict[str, Any]:
targets = []
for i, sector in enumerate(req.sectors):
sector_info = SECTOR_PAIN_MAP.get(sector, {})
for j, city in enumerate(req.cities[:3]):
idx = i * 3 + j
if idx >= req.daily_target_count:
break
target = {
"id": str(uuid4())[:8],
"company": f"[{sector}_{city}_{j+1}]",
"sector": sector,
"city": city,
"pain_hypothesis": sector_info.get("pain_ar", ""),
"angle": sector_info.get("angle_ar", ""),
"roi_estimate": sector_info.get("roi_ar", ""),
"priority": "P0" if sector in ("real_estate", "construction", "agency") else "P1",
"channel": "email",
"approval_required": req.approval_required,
"status": "ready",
}
targets.append(target)
return {
"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
"total_generated": len(targets),
"sectors": req.sectors,
"cities": req.cities,
"batch_size": req.batch_size,
"approval_required": req.approval_required,
"targets": targets[:req.daily_target_count],
}
REPLY_CATEGORIES = {
"interested": {"next": "propose_demo", "auto_reply": True},
"ask_price": {"next": "explain_pilot_499", "auto_reply": True},
"ask_details": {"next": "send_brief_explanation", "auto_reply": True},
"ask_demo": {"next": "send_calendly", "auto_reply": True},
"not_now": {"next": "schedule_followup_30d", "auto_reply": True},
"unsubscribe": {"next": "opt_out_suppress", "auto_reply": False},
"objection_budget": {"next": "explain_roi_pilot", "auto_reply": True},
"objection_ai": {"next": "explain_manual_first", "auto_reply": True},
"objection_privacy": {"next": "human_review", "auto_reply": False},
"already_has_crm": {"next": "position_as_layer", "auto_reply": True},
"partnership": {"next": "route_partner_flow", "auto_reply": False},
"angry": {"next": "apologize_opt_out", "auto_reply": False},
}
REPLY_RESPONSES = {
"interested": "ممتاز! يناسبك نحجز 20 دقيقة هذا الأسبوع؟\ncalendly.com/sami-assiri11/dealix-demo",
"ask_price": "نبدأها كـ pilot بسيط لمدة 7 أيام بـ 499 ريال.\nنجرب على 10-25 lead، ونقيس النتائج.\nلو ما عجبك — استرداد كامل.",
"ask_details": "Dealix يساعدكم في:\n- الرد السريع على الـ leads\n- تأهيل العميل بأسئلة واضحة\n- حجز موعد أو تحويله لفريق المبيعات\n- تقرير يومي مختصر\n\nأفضل بداية: pilot 7 أيام. يناسبك أشرح أكثر؟",
"ask_demo": "تمام! احجز الوقت المناسب:\ncalendly.com/sami-assiri11/dealix-demo\n\nأو قلي وقتين يناسبونك وأنا أنسّق.",
"not_now": "تمام، شكراً على وقتك. أتواصل معك بعد شهر لو مناسب؟",
"objection_budget": "فاهم. لذلك نبدأ بـ pilot بسيط بـ 499 ريال فقط.\nلو ما شفت قيمة خلال 7 أيام — استرداد كامل.",
"already_has_crm": "ممتاز، Dealix ما يستبدل الـ CRM.\nهو طبقة قبله: يرد على الـ lead، يأهله، يحجز الموعد،\nوبعدها يسلّم البيانات لفريقكم أو للـ CRM.",
"unsubscribe": "تم، لن أتواصل معكم مرة ثانية. شكراً على وقتكم.",
}
class ClassifyReplyRequest(BaseModel):
reply_text: str
company: str = ""
original_sector: str = ""
@router.post("/reply/classify")
async def classify_reply(req: ClassifyReplyRequest) -> Dict[str, Any]:
text = req.reply_text.lower()
if any(w in text for w in ["إيقاف", "stop", "unsubscribe", "لا تتواصل", "remove"]):
cat = "unsubscribe"
elif any(w in text for w in ["كم السعر", "كم التكلفة", "how much", "pricing", "أسعار"]):
cat = "ask_price"
elif any(w in text for w in ["عرض", "demo", "ديمو", "أوريني", "شرح"]):
cat = "ask_demo"
elif any(w in text for w in ["مهتم", "interested", "أبي أجرب", "نجرب", "تمام"]):
cat = "interested"
elif any(w in text for w in ["تفاصيل", "details", "أكثر", "وش بالضبط"]):
cat = "ask_details"
elif any(w in text for w in ["لاحقاً", "later", "مو الحين", "بعدين"]):
cat = "not_now"
elif any(w in text for w in ["ميزانية", "budget", "غالي", "مكلف"]):
cat = "objection_budget"
elif any(w in text for w in ["CRM", "crm", "نظام", "عندنا حل"]):
cat = "already_has_crm"
elif any(w in text for w in ["شراكة", "partner", "وكالة", "نبيع"]):
cat = "partnership"
elif any(w in text for w in ["خصوصية", "privacy", "بيانات", "PDPL"]):
cat = "objection_privacy"
elif any(w in text for w in ["ذكاء", "AI", "عربي طبيعي", "مضبوط"]):
cat = "objection_ai"
elif any(w in text for w in ["زعلان", "angry", "spam", "مزعج"]):
cat = "angry"
else:
cat = "ask_details"
info = REPLY_CATEGORIES.get(cat, {"next": "human_review", "auto_reply": False})
response = REPLY_RESPONSES.get(cat, "شكراً على ردك. براجع وأرد عليك قريب.")
return {
"category": cat,
"next_action": info["next"],
"auto_reply_allowed": info["auto_reply"],
"suggested_response": response,
"human_review_required": not info["auto_reply"],
"company": req.company,
}

View File

@ -17,7 +17,7 @@ from app.openclaw.observability_bridge import observability_bridge
from app.openclaw.policy import classify_action
from app.openclaw.task_router import task_router
from app.services.contract_intelligence_service import contract_intelligence_service
from app.services.executive_roi_service import executive_roi_service
from app.services.executive_roi_service import executive_room_service as executive_roi_service
from app.services.predictive_revenue_service import predictive_revenue_service
from app.openclaw.plugins.salesforce_agentforce_plugin import SalesforceAgentforcePlugin
from app.openclaw.plugins.whatsapp_plugin import WhatsAppCloudPlugin

View File

@ -0,0 +1,385 @@
"""Draft Queue API — review, approve, send, track outreach drafts.
All outreach starts as drafts. Sami reviews in dashboard, approves
batch, then system sends via existing Celery tasks.
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import select, func, update
from sqlalchemy.ext.asyncio import AsyncSession
logger = logging.getLogger("dealix.drafts")
router = APIRouter(prefix="/drafts", tags=["Draft Queue"])
async def _get_db():
from app.database import get_db
async for session in get_db():
yield session
class DraftFilter(BaseModel):
status: Optional[str] = "draft"
channel: Optional[str] = None
batch_id: Optional[str] = None
sector: Optional[str] = None
limit: int = 50
class ApproveBatchRequest(BaseModel):
batch_id: str
class LogReplyRequest(BaseModel):
reply_text: str
class EditDraftRequest(BaseModel):
subject: Optional[str] = None
body: Optional[str] = None
channel: Optional[str] = None
contact_email: Optional[str] = None
contact_phone: Optional[str] = None
@router.get("/")
async def list_drafts(
status: Optional[str] = Query("draft"),
channel: Optional[str] = Query(None),
batch_id: Optional[str] = Query(None),
limit: int = Query(50, ge=1, le=200),
db: AsyncSession = Depends(_get_db),
) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
stmt = select(OutreachDraft)
if status:
stmt = stmt.where(OutreachDraft.status == status)
if channel:
stmt = stmt.where(OutreachDraft.channel == channel)
if batch_id:
stmt = stmt.where(OutreachDraft.batch_id == batch_id)
stmt = stmt.order_by(OutreachDraft.created_at.desc()).limit(limit)
result = await db.execute(stmt)
rows = list(result.scalars().all())
return {
"drafts": [r.to_dict() for r in rows],
"count": len(rows),
"filter": {"status": status, "channel": channel, "batch_id": batch_id},
}
@router.get("/stats")
async def draft_stats(db: AsyncSession = Depends(_get_db)) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
select(OutreachDraft.status, func.count(OutreachDraft.id))
.group_by(OutreachDraft.status)
)
counts = {row[0]: row[1] for row in result.all()}
return {
"total": sum(counts.values()),
"draft": counts.get("draft", 0),
"approved": counts.get("approved", 0),
"sent": counts.get("sent", 0),
"replied": counts.get("replied", 0),
"opted_out": counts.get("opted_out", 0),
"bounced": counts.get("bounced", 0),
"skipped": counts.get("skipped", 0),
}
@router.get("/{draft_id}")
async def get_draft(draft_id: str, db: AsyncSession = Depends(_get_db)) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
select(OutreachDraft).where(OutreachDraft.id == draft_id)
)
draft = result.scalar_one_or_none()
if not draft:
raise HTTPException(status_code=404, detail="Draft not found")
d = draft.to_dict()
d["body"] = draft.body
d["followup_2d"] = draft.followup_2d
d["followup_5d"] = draft.followup_5d
d["call_script"] = draft.call_script
return d
@router.post("/{draft_id}/approve")
async def approve_draft(draft_id: str, db: AsyncSession = Depends(_get_db)) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
select(OutreachDraft).where(OutreachDraft.id == draft_id)
)
draft = result.scalar_one_or_none()
if not draft:
raise HTTPException(status_code=404, detail="Draft not found")
if draft.status != "draft":
return {"id": str(draft.id), "status": draft.status, "message": "already processed"}
draft.status = "approved"
draft.approved_at = datetime.now(timezone.utc)
await db.commit()
return {"id": str(draft.id), "status": "approved"}
@router.post("/approve-batch")
async def approve_batch(
req: ApproveBatchRequest, db: AsyncSession = Depends(_get_db)
) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
update(OutreachDraft)
.where(OutreachDraft.batch_id == req.batch_id, OutreachDraft.status == "draft")
.values(status="approved", approved_at=datetime.now(timezone.utc))
)
await db.commit()
return {"batch_id": req.batch_id, "approved_count": result.rowcount}
@router.post("/{draft_id}/send")
async def send_draft(draft_id: str, db: AsyncSession = Depends(_get_db)) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
select(OutreachDraft).where(OutreachDraft.id == draft_id)
)
draft = result.scalar_one_or_none()
if not draft:
raise HTTPException(status_code=404, detail="Draft not found")
if draft.status not in ("approved", "draft"):
return {"id": str(draft.id), "status": draft.status, "message": "not sendable"}
send_result = {"channel": draft.channel, "status": "pending"}
if draft.channel == "email" and draft.contact_email:
try:
from app.integrations.email_sender import send_email
r = await send_email(draft.contact_email, draft.subject, draft.body)
send_result = {"channel": "email", "status": "sent", "result": r}
except Exception as exc:
send_result = {"channel": "email", "status": "failed", "error": str(exc)[:200]}
elif draft.channel == "whatsapp" and draft.contact_phone:
try:
from app.api.v1.outreach_engine import _send_via_ultramsg, _format_phone
r = await _send_via_ultramsg(draft.contact_phone, draft.body)
if "error" not in r:
send_result = {"channel": "whatsapp_ultramsg", "status": "sent", "result": r}
else:
from app.integrations.whatsapp import send_whatsapp_message
r2 = await send_whatsapp_message(draft.contact_phone, draft.body)
send_result = {"channel": "whatsapp_business_api", "status": "sent", "result": r2}
except Exception as exc:
send_result = {"channel": "whatsapp", "status": "failed", "error": str(exc)[:200]}
elif draft.channel == "sms" and draft.contact_phone:
try:
from app.integrations.sms import send_sms
r = await send_sms(draft.contact_phone, draft.body)
send_result = {"channel": "sms", "status": "sent", "result": r}
except Exception as exc:
send_result = {"channel": "sms", "status": "failed", "error": str(exc)[:200]}
elif draft.channel == "linkedin":
send_result = {
"channel": "linkedin",
"status": "manual_required",
"message": "Copy the message and send manually on LinkedIn",
}
if send_result.get("status") == "sent":
draft.status = "sent"
draft.sent_at = datetime.now(timezone.utc)
elif send_result.get("status") == "failed":
draft.next_action = f"send_failed: {send_result.get('error', '')[:100]}"
await db.commit()
return {"id": str(draft.id), **send_result}
@router.post("/{draft_id}/skip")
async def skip_draft(draft_id: str, db: AsyncSession = Depends(_get_db)) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
select(OutreachDraft).where(OutreachDraft.id == draft_id)
)
draft = result.scalar_one_or_none()
if not draft:
raise HTTPException(status_code=404, detail="Draft not found")
draft.status = "skipped"
await db.commit()
return {"id": str(draft.id), "status": "skipped"}
@router.patch("/{draft_id}")
async def edit_draft(
draft_id: str, req: EditDraftRequest, db: AsyncSession = Depends(_get_db)
) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
result = await db.execute(
select(OutreachDraft).where(OutreachDraft.id == draft_id)
)
draft = result.scalar_one_or_none()
if not draft:
raise HTTPException(status_code=404, detail="Draft not found")
if draft.status != "draft":
raise HTTPException(status_code=400, detail="Can only edit drafts, not sent/approved")
for field, value in req.model_dump(exclude_none=True).items():
setattr(draft, field, value)
await db.commit()
return {"id": str(draft.id), "status": "edited", "updated_fields": list(req.model_dump(exclude_none=True).keys())}
@router.post("/send-approved-batch")
async def send_approved_batch(
channel: str = "whatsapp",
batch_size: int = 10,
db: AsyncSession = Depends(_get_db),
) -> Dict[str, Any]:
"""Send up to batch_size approved drafts via specified channel.
Uses Ultramsg for WhatsApp (fallback to Business API),
SMTP for email, Unifonic for SMS. LinkedIn = manual only.
"""
from app.models.outreach_draft import OutreachDraft
stmt = (
select(OutreachDraft)
.where(
OutreachDraft.status == "approved",
OutreachDraft.channel == channel,
)
.order_by(OutreachDraft.approved_at.asc())
.limit(batch_size)
)
result = await db.execute(stmt)
drafts = list(result.scalars().all())
sent = 0
failed = 0
results = []
for draft in drafts:
send_result = {}
if channel == "whatsapp" and draft.contact_phone:
try:
from app.api.v1.outreach_engine import _send_via_ultramsg
r = await _send_via_ultramsg(draft.contact_phone, draft.body)
if "error" not in r:
send_result = {"status": "sent", "provider": "ultramsg", "result": r}
draft.status = "sent"
draft.sent_at = datetime.now(timezone.utc)
sent += 1
else:
send_result = {"status": "failed", "error": str(r)}
failed += 1
except Exception as exc:
send_result = {"status": "failed", "error": str(exc)[:100]}
failed += 1
elif channel == "email" and draft.contact_email:
try:
from app.integrations.email_sender import send_email
r = await send_email(draft.contact_email, draft.subject, draft.body)
send_result = {"status": "sent", "provider": "smtp", "result": r}
draft.status = "sent"
draft.sent_at = datetime.now(timezone.utc)
sent += 1
except Exception as exc:
send_result = {"status": "failed", "error": str(exc)[:100]}
failed += 1
elif channel == "sms" and draft.contact_phone:
try:
from app.integrations.sms import send_sms
r = await send_sms(draft.contact_phone, draft.body)
send_result = {"status": "sent", "provider": "unifonic", "result": r}
draft.status = "sent"
draft.sent_at = datetime.now(timezone.utc)
sent += 1
except Exception as exc:
send_result = {"status": "failed", "error": str(exc)[:100]}
failed += 1
elif channel == "linkedin":
send_result = {"status": "manual_required", "message": "Copy from dashboard and send on LinkedIn"}
results.append({
"id": str(draft.id),
"company": draft.company,
**send_result,
})
await db.commit()
return {
"channel": channel,
"batch_size": batch_size,
"sent": sent,
"failed": failed,
"manual": len([r for r in results if r.get("status") == "manual_required"]),
"results": results,
}
@router.post("/{draft_id}/log-reply")
async def log_reply(
draft_id: str, req: LogReplyRequest, db: AsyncSession = Depends(_get_db)
) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
from app.api.v1.automation import classify_reply, ClassifyReplyRequest
result = await db.execute(
select(OutreachDraft).where(OutreachDraft.id == draft_id)
)
draft = result.scalar_one_or_none()
if not draft:
raise HTTPException(status_code=404, detail="Draft not found")
classification = await classify_reply(
ClassifyReplyRequest(
reply_text=req.reply_text,
company=draft.company,
original_sector=draft.sector,
)
)
draft.status = "replied"
draft.replied_at = datetime.now(timezone.utc)
draft.reply_text = req.reply_text
draft.reply_category = classification["category"]
draft.next_action = classification["next_action"]
if classification["category"] == "unsubscribe":
draft.status = "opted_out"
draft.next_action = "suppressed — no further contact"
await db.commit()
return {
"id": str(draft.id),
"reply_category": classification["category"],
"suggested_response": classification["suggested_response"],
"next_action": classification["next_action"],
"auto_reply_allowed": classification["auto_reply_allowed"],
}

View File

@ -0,0 +1,162 @@
"""Follow-up Scheduler — generates follow-up drafts for unreplied outreach.
Checks sent drafts that haven't received replies after 2/5/10 days
and creates new follow-up drafts linked to the original.
"""
from __future__ import annotations
import logging
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List
from uuid import uuid4
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession
logger = logging.getLogger("dealix.followups")
router = APIRouter(prefix="/followups", tags=["Follow-ups"])
async def _get_db():
from app.database import get_db
async for session in get_db():
yield session
@router.get("/due")
async def list_due_followups(
days_since_sent: int = Query(2, ge=1, le=30),
limit: int = Query(50, ge=1, le=200),
db: AsyncSession = Depends(_get_db),
) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
cutoff = datetime.now(timezone.utc) - timedelta(days=days_since_sent)
stmt = (
select(OutreachDraft)
.where(
and_(
OutreachDraft.status == "sent",
OutreachDraft.sent_at <= cutoff,
OutreachDraft.reply_text.is_(None),
)
)
.order_by(OutreachDraft.sent_at.asc())
.limit(limit)
)
result = await db.execute(stmt)
rows = list(result.scalars().all())
due = []
for row in rows:
days_elapsed = (datetime.now(timezone.utc) - row.sent_at).days if row.sent_at else 0
followup_text = ""
followup_type = ""
if days_elapsed >= 10:
followup_text = row.followup_5d or "آخر متابعة — لو مو الوقت المناسب أفهم تماماً. شكراً."
followup_type = "day_10_breakup"
elif days_elapsed >= 5:
followup_text = row.followup_5d or row.followup_2d or ""
followup_type = "day_5_value"
elif days_elapsed >= 2:
followup_text = row.followup_2d or ""
followup_type = "day_2_reminder"
if followup_text:
due.append({
"original_draft_id": str(row.id),
"company": row.company,
"channel": row.channel,
"contact_email": row.contact_email,
"contact_phone": row.contact_phone,
"days_since_sent": days_elapsed,
"followup_type": followup_type,
"followup_text": followup_text,
"sector": row.sector,
})
return {
"due_count": len(due),
"cutoff_days": days_since_sent,
"followups": due,
}
class GenerateFollowupsRequest(BaseModel):
days_since_sent: int = 2
max_followups: int = 20
@router.post("/generate")
async def generate_followup_drafts(
req: GenerateFollowupsRequest,
db: AsyncSession = Depends(_get_db),
) -> Dict[str, Any]:
from app.models.outreach_draft import OutreachDraft
cutoff = datetime.now(timezone.utc) - timedelta(days=req.days_since_sent)
stmt = (
select(OutreachDraft)
.where(
and_(
OutreachDraft.status == "sent",
OutreachDraft.sent_at <= cutoff,
OutreachDraft.reply_text.is_(None),
)
)
.order_by(OutreachDraft.sent_at.asc())
.limit(req.max_followups)
)
result = await db.execute(stmt)
originals = list(result.scalars().all())
created = 0
batch_id = f"followup_{datetime.now(timezone.utc).strftime('%Y%m%d')}_{str(uuid4())[:6]}"
for orig in originals:
days_elapsed = (datetime.now(timezone.utc) - orig.sent_at).days if orig.sent_at else 0
if days_elapsed >= 10:
body = orig.followup_5d or "آخر متابعة — لو مناسب نتكلم، أنا موجود. لو لا، شكراً على وقتكم."
subject = f"متابعة أخيرة: {orig.company}"
elif days_elapsed >= 5:
body = orig.followup_5d or orig.followup_2d or ""
subject = f"متابعة: {orig.company}"
else:
body = orig.followup_2d or ""
subject = f"متابعة سريعة: {orig.company}"
if not body:
continue
followup = OutreachDraft(
batch_id=batch_id,
company=orig.company,
contact_name=orig.contact_name,
contact_email=orig.contact_email,
contact_phone=orig.contact_phone,
channel=orig.channel,
subject=subject,
body=body,
sector=orig.sector,
city=orig.city,
fit_score=orig.fit_score,
risk_score=orig.risk_score,
status="draft",
approval_required=True,
source=f"followup_day_{days_elapsed}_of_{str(orig.id)[:8]}",
)
db.add(followup)
created += 1
if created:
await db.commit()
return {
"batch_id": batch_id,
"followups_created": created,
"originals_checked": len(originals),
}

View File

@ -0,0 +1,217 @@
"""Founder Outreach — hyper-personalized emails from Sami as founder.
Each email researches the company's weakness, estimates lost revenue,
and writes a personal message showing exact ROI Dealix can deliver.
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from fastapi import APIRouter
from pydantic import BaseModel
logger = logging.getLogger("dealix.founder_outreach")
router = APIRouter(prefix="/founder-outreach", tags=["Founder Outreach"])
SECTOR_WEAKNESS_MAP = {
"real_estate": {
"weakness_ar": "شركات العقار تستقبل 50-200 استفسار/شهر عن الأسعار والمواقع. المشكلة: 60-70% ما يُتابع خلال أول ساعة = العميل يروح للمنافس",
"weakness_en": "Real estate companies receive 50-200 inquiries/month about prices and locations. Problem: 60-70% don't get a response within the first hour = client goes to competitor",
"revenue_loss_ar": "لو عندكم 100 استفسار/شهر و60% تضيع = 60 فرصة ضائعة. لو 10% منها كانت بتتحول = 6 صفقات × متوسط 50,000 ريال = ~300,000 ريال ضائعة سنوياً",
"revenue_loss_en": "If you get 100 inquiries/month and 60% are lost = 60 missed opportunities. If 10% would convert = 6 deals × avg 50,000 SAR = ~300,000 SAR lost annually",
"dealix_impact_ar": "Dealix يرد خلال 45 ثانية ويحفظ 30-40% من الفرص الضائعة = ~120,000 ريال إضافية/سنة. تكلفة Dealix = 990 ريال/شهر = ROI 10x",
"dealix_impact_en": "Dealix responds in 45 seconds and saves 30-40% of lost opportunities = ~120,000 SAR additional/year. Dealix costs 990 SAR/month = 10x ROI",
},
"construction": {
"weakness_ar": "شركات المقاولات تستقبل طلبات عروض أسعار يومياً. المشكلة: فرز الطلبات الجادة من غيرها ياخذ ساعات من المهندسين — وقت ممكن يُصرف في التنفيذ",
"weakness_en": "Construction companies receive quote requests daily. Problem: Sorting serious from non-serious requests takes hours of engineer time",
"revenue_loss_ar": "لو فريق المبيعات يقضي 2 ساعة/يوم في فرز الطلبات = 40 ساعة/شهر = تكلفة ~8,000 ريال/شهر من وقت الفريق",
"revenue_loss_en": "If sales team spends 2 hours/day sorting requests = 40 hours/month = ~8,000 SAR/month in team time cost",
"dealix_impact_ar": "Dealix يستقبل الطلب، يسأل 3 أسئلة تأهيل، ويصنّف الجدية تلقائياً. يوفّر 80% من وقت الفرز = ~6,400 ريال/شهر. ROI: 6.5x",
"dealix_impact_en": "Dealix receives requests, asks 3 qualifying questions, and auto-classifies urgency. Saves 80% of sorting time = ~6,400 SAR/month. ROI: 6.5x",
},
"hospitality": {
"weakness_ar": "الفنادق والقاعات تستقبل استفسارات حجز يومياً. المشكلة: كل ساعة تأخير = احتمال 50% العميل يحجز عند المنافس",
"weakness_en": "Hotels and event venues receive booking inquiries daily. Problem: every hour of delay = 50% chance the client books with a competitor",
"revenue_loss_ar": "لو 10 استفسارات/أسبوع تضيع بسبب تأخر الرد × متوسط حجز 5,000 ريال = 200,000 ريال ضائعة/سنة",
"revenue_loss_en": "If 10 inquiries/week are lost due to slow response × avg booking 5,000 SAR = 200,000 SAR lost/year",
"dealix_impact_ar": "Dealix يرد فوراً، يسأل عن التاريخ والعدد والميزانية، ويحجز مبدئي. يحفظ 40% من الحجوزات الضائعة = ~80,000 ريال/سنة",
"dealix_impact_en": "Dealix responds instantly, asks about date, guest count, and budget, and makes a preliminary booking. Saves 40% of lost bookings = ~80,000 SAR/year",
},
"agency": {
"weakness_ar": "الوكالات تصرف على إعلانات عملائها لكن المتابعة على الـ leads ضعيفة. النتيجة: العميل يلوم الوكالة على ضعف النتائج",
"weakness_en": "Agencies spend on client ads but lead follow-up is weak. Result: client blames agency for poor results",
"revenue_loss_ar": "كل عميل وكالة يدفع 5,000-20,000 ريال/شهر. لو عميل واحد يطلع بسبب ضعف المتابعة = خسارة 60,000-240,000 ريال/سنة",
"revenue_loss_en": "Each agency client pays 5,000-20,000 SAR/month. If one client leaves due to poor follow-up = loss of 60,000-240,000 SAR/year",
"dealix_impact_ar": "Dealix يصير خدمة جديدة تبيعونها: رد ذكي + تأهيل + حجز. Setup fee 3,000 + MRR 990/عميل = إيراد جديد بدل خسارة عميل",
"dealix_impact_en": "Dealix becomes a new service you sell: smart response + qualification + booking. Setup fee 3,000 + MRR 990/client = new revenue instead of losing a client",
},
"saas": {
"weakness_ar": "شركات SaaS تجيها leads من الموقع والإعلانات لكن فريق المبيعات صغير. المشكلة: 30-50% من الـ leads تبرد خلال أول 24 ساعة",
"weakness_en": "SaaS companies get leads from website and ads but sales team is small. Problem: 30-50% of leads go cold within 24 hours",
"revenue_loss_ar": "لو 200 lead/شهر و40% تبرد = 80 lead ضائع. لو 5% كانت بتشتري × متوسط 5,000 ريال/سنة = 20,000 ريال ضائعة/شهر",
"revenue_loss_en": "If 200 leads/month and 40% go cold = 80 lost leads. If 5% would buy × avg 5,000 SAR/year = 20,000 SAR lost/month",
"dealix_impact_ar": "Dealix يرد بالعربي خلال 45 ثانية على كل lead، يؤهل، ويحجز demo. يحفظ 30% من الضائع = ~6,000 ريال إضافية/شهر. ROI: 6x",
"dealix_impact_en": "Dealix responds in Arabic within 45 seconds to every lead, qualifies, and books demos. Saves 30% of lost leads = ~6,000 SAR additional/month. ROI: 6x",
},
"logistics": {
"weakness_ar": "شركات الشحن تستقبل طلبات أسعار متكررة. المشكلة: كل طلب ما يُتابع = شحنة تروح للمنافس مباشرة",
"weakness_en": "Shipping companies receive repeated quote requests. Problem: every unfollowed request = shipment goes directly to competitor",
"revenue_loss_ar": "لو 5 طلبات/أسبوع تضيع × متوسط 3,000 ريال/شحنة = 60,000 ريال ضائعة/شهر",
"revenue_loss_en": "If 5 requests/week are lost × avg 3,000 SAR/shipment = 60,000 SAR lost/month",
"dealix_impact_ar": "Dealix يستقبل الطلب، يسأل عن نوع الشحنة والوجهة، ويعطي تقدير أولي فوراً. يحفظ 40% = ~24,000 ريال/شهر",
"dealix_impact_en": "Dealix receives requests, asks about shipment type and destination, gives instant estimate. Saves 40% = ~24,000 SAR/month",
},
}
class FounderEmailRequest(BaseModel):
company: str
sector: str
contact_name: str = ""
contact_email: str = ""
city: str = ""
website: str = ""
signals: List[str] = []
language: str = "ar"
estimated_monthly_leads: int = 100
@router.post("/generate")
async def generate_founder_email(req: FounderEmailRequest) -> Dict[str, Any]:
"""Generate a hyper-personalized founder email that targets the company's weakness."""
sector_data = SECTOR_WEAKNESS_MAP.get(req.sector, SECTOR_WEAKNESS_MAP.get("saas", {}))
lang = req.language
if lang == "en":
return _build_en(req, sector_data)
name = req.contact_name or f"فريق {req.company}"
weakness = sector_data.get("weakness_ar", "")
revenue_loss = sector_data.get("revenue_loss_ar", "")
impact = sector_data.get("dealix_impact_ar", "")
signal_line = ""
if "hubspot" in [s.lower() for s in req.signals]:
signal_line = f"شفت إن {req.company} تستخدمون HubSpot — يعني عندكم leads تجي من الموقع. "
elif "whatsapp_widget" in [s.lower() for s in req.signals]:
signal_line = f"لاحظت إن عندكم واتساب كقناة رئيسية للعملاء. "
subject = f"{name} — فرصة توفير {req.estimated_monthly_leads * 50} ريال/شهر لـ {req.company}"
body = f"""مرحباً {name}،
أنا سامي العسيري، مؤسس Dealix.
{signal_line}أكتب لك شخصياً لأن لاحظت شي مهم عن {req.company}:
📊 المشكلة:
{weakness}
💰 التكلفة الحقيقية:
{revenue_loss}
🎯 الحل:
{impact}
ما أبي أبيعك شي الحين أبي أوريك بالأرقام.
نسوي pilot 7 أيام على leads حقيقية عندكم:
نرد على كل استفسار خلال 45 ثانية
نسأل أسئلة التأهيل المهمة لقطاعكم
نحجز المواعيد أو نحوّل للمبيعات
نرسل تقرير يومي بالنتائج
السعر: 499 ريال فقط (مع ضمان استرداد كامل).
يناسبك 15 دقيقة هذا الأسبوع أوريك النظام على سيناريو من شغلكم؟
📅 calendly.com/sami-assiri11/dealix-demo
وإذا عندك سؤال رد على هالإيميل مباشرة. أنا شخصياً أرد.
سامي العسيري
مؤسس Dealix
dealix.me
+966 59 778 8539
إذا ما يناسبكم هالنوع من الرسائل، اكتبوا "إيقاف" وما بنتواصل مرة ثانية."""
return {
"type": "founder_personal",
"company": req.company,
"language": "ar",
"subject": subject,
"body": body,
"weakness_targeted": weakness[:100],
"revenue_loss_estimate": revenue_loss[:100],
"dealix_roi": impact[:100],
"opt_out_included": True,
"word_count": len(body.split()),
}
def _build_en(req: FounderEmailRequest, sector_data: Dict) -> Dict[str, Any]:
name = req.contact_name or f"{req.company} team"
weakness = sector_data.get("weakness_en", "")
revenue_loss = sector_data.get("revenue_loss_en", "")
impact = sector_data.get("dealix_impact_en", "")
signal_line = ""
if "hubspot" in [s.lower() for s in req.signals]:
signal_line = f"I noticed {req.company} uses HubSpot — meaning you have leads coming from your website. "
subject = f"{name} — opportunity to save {req.estimated_monthly_leads * 50} SAR/month for {req.company}"
body = f"""Hi {name},
I'm Sami Alassiri, founder of Dealix.
{signal_line}I'm writing personally because I noticed something important about {req.company}:
📊 The Problem:
{weakness}
💰 The Real Cost:
{revenue_loss}
🎯 The Solution:
{impact}
I'm not trying to sell you anything right now — I want to show you with numbers.
We'll run a 7-day pilot on your actual leads:
Respond to every inquiry within 45 seconds
Ask the right qualifying questions for your industry
Book meetings or route to sales
Send you a daily results report
Price: 499 SAR only (with full money-back guarantee).
Do you have 15 minutes this week? I'll show you the system on a scenario from your business.
📅 calendly.com/sami-assiri11/dealix-demo
Questions? Reply to this email directly. I personally respond.
Sami Alassiri
Founder, Dealix
dealix.me
+966 59 778 8539
To stop receiving these emails, reply "STOP"."""
return {
"type": "founder_personal",
"company": req.company,
"language": "en",
"subject": subject,
"body": body,
"weakness_targeted": weakness[:100],
"revenue_loss_estimate": revenue_loss[:100],
"dealix_roi": impact[:100],
"opt_out_included": True,
"word_count": len(body.split()),
}

View File

@ -0,0 +1,183 @@
"""Full OS API — unified deal lifecycle orchestration.
Single endpoint processes any event (inbound message, reply, booking,
payment) and returns: next stage, actions to take, response message,
and whether human approval is needed.
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/os", tags=["Full OS"])
class ProcessEventRequest(BaseModel):
lead_id: str = ""
phone: str = ""
email: str = ""
company: str = ""
sector: str = ""
source: str = "whatsapp_inbound"
message: str = ""
current_stage: str = "new_lead"
event_type: str = "inbound_message"
class BulkProcessRequest(BaseModel):
events: List[ProcessEventRequest]
@router.post("/process")
async def process_event(req: ProcessEventRequest) -> Dict[str, Any]:
"""Process a single event through the deal lifecycle state machine.
Returns: new_stage, actions, response_message_ar, human_approval_required.
If auto_send_allowed=True, the response can be sent automatically.
If human_approval_required=True, create a draft for Sami to review.
"""
from app.services.full_os_orchestrator import orchestrator, OrchestratorEvent
event = OrchestratorEvent(
lead_id=req.lead_id,
phone=req.phone,
email=req.email,
company=req.company,
sector=req.sector,
source=req.source,
message=req.message,
current_stage=req.current_stage,
event_type=req.event_type,
)
return orchestrator.process_event(event)
@router.post("/process-and-act")
async def process_and_act(req: ProcessEventRequest) -> Dict[str, Any]:
"""Process event AND execute the first safe action.
If auto_send_allowed: sends WhatsApp response via Ultramsg.
If human_approval_required: creates a draft for review.
Always logs the activity.
"""
from app.services.full_os_orchestrator import orchestrator, OrchestratorEvent
event = OrchestratorEvent(
lead_id=req.lead_id,
phone=req.phone,
email=req.email,
company=req.company,
sector=req.sector,
source=req.source,
message=req.message,
current_stage=req.current_stage,
event_type=req.event_type,
)
result = orchestrator.process_event(event)
execution = {"action_taken": "none", "send_result": None, "draft_created": False}
if result.get("auto_send_allowed") and result.get("response_message_ar") and req.phone:
if "send_whatsapp" in result.get("actions", []):
try:
from app.services.whatsapp_multi_provider import send_whatsapp_smart
send_result = await send_whatsapp_smart(req.phone, result["response_message_ar"])
execution = {
"action_taken": "whatsapp_sent",
"send_result": send_result,
"draft_created": False,
}
except Exception as exc:
execution = {
"action_taken": "whatsapp_failed",
"error": str(exc)[:200],
"draft_created": False,
}
elif result.get("human_approval_required") and result.get("response_message_ar"):
try:
from app.models.outreach_draft import OutreachDraft
from app.database import async_session
async with async_session() as session:
draft = OutreachDraft(
batch_id=f"os_{result['lead_id']}",
company=req.company,
contact_phone=req.phone,
contact_email=req.email,
channel="whatsapp" if req.phone else "email",
subject=f"[{result['new_stage']}] {req.company}",
body=result["response_message_ar"],
sector=req.sector,
status="draft",
approval_required=True,
source="full_os_orchestrator",
)
session.add(draft)
await session.commit()
execution = {
"action_taken": "draft_created",
"draft_id": str(draft.id),
"draft_created": True,
}
except Exception:
execution = {"action_taken": "draft_failed", "draft_created": False}
return {**result, "execution": execution}
@router.post("/bulk-process")
async def bulk_process(req: BulkProcessRequest) -> Dict[str, Any]:
"""Process multiple events at once (for batch imports)."""
from app.services.full_os_orchestrator import orchestrator, OrchestratorEvent
results = []
for event_req in req.events:
event = OrchestratorEvent(
lead_id=event_req.lead_id,
phone=event_req.phone,
email=event_req.email,
company=event_req.company,
sector=event_req.sector,
source=event_req.source,
message=event_req.message,
current_stage=event_req.current_stage,
event_type=event_req.event_type,
)
results.append(orchestrator.process_event(event))
return {
"processed": len(results),
"results": results,
}
@router.get("/whatsapp-providers")
async def whatsapp_provider_status() -> Dict[str, Any]:
"""Check which WhatsApp providers are configured."""
from app.services.whatsapp_multi_provider import check_providers
return await check_providers()
@router.post("/test-send")
async def test_whatsapp_send(phone: str, message: str = "اختبار Dealix — النظام شغّال 🚀") -> Dict[str, Any]:
"""Test WhatsApp send via all configured providers."""
from app.services.whatsapp_multi_provider import send_whatsapp_smart
return await send_whatsapp_smart(phone, message)
@router.get("/stages")
async def list_stages() -> Dict[str, Any]:
"""List all deal lifecycle stages with their possible transitions."""
from app.services.full_os_orchestrator import STAGE_TRANSITIONS, STAGE_AUTO_ACTIONS, STAGE_MESSAGES_AR
stages = {}
for stage, transitions in STAGE_TRANSITIONS.items():
stages[stage.value] = {
"transitions": {k: v.value for k, v in transitions.items()},
"auto_actions": [a.value for a in STAGE_AUTO_ACTIONS.get(stage, [])],
"message_template": STAGE_MESSAGES_AR.get(stage, ""),
}
return {"stages": stages, "total": len(stages)}

View File

@ -101,8 +101,8 @@ def _format_phone(phone: str) -> str:
async def _send_via_ultramsg(phone: str, message: str) -> dict:
"""Send a message via Ultramsg API."""
instance_id = os.getenv("ULTRAMSG_INSTANCE_ID", "instance168132")
token = os.getenv("ULTRAMSG_TOKEN", "7azj2ss74wpg9jwp")
instance_id = os.getenv("ULTRAMSG_INSTANCE_ID", "")
token = os.getenv("ULTRAMSG_TOKEN", "")
if not instance_id or not token:
return {"error": "Ultramsg not configured"}

View File

@ -136,3 +136,23 @@ api_router.include_router(channels_router.router)
# ── Pricing & Checkout — Moyasar-powered payment flow ────────
from app.api.v1 import pricing as pricing_router
api_router.include_router(pricing_router.router)
# ── Automation — Daily targeting + email + reply classification ─
from app.api.v1 import automation as automation_router
api_router.include_router(automation_router.router)
# ── Draft Queue — review, approve, send outreach drafts ────────
from app.api.v1 import drafts as drafts_router
api_router.include_router(drafts_router.router)
# ── Follow-ups — auto-generate follow-up drafts for unreplied ──
from app.api.v1 import followups as followups_router
api_router.include_router(followups_router.router)
# ── Full OS — unified deal lifecycle orchestration ─────────────
from app.api.v1 import full_os as full_os_router
api_router.include_router(full_os_router.router)
# ── Founder Outreach — hyper-personalized founder emails ───────
from app.api.v1 import founder_outreach as founder_outreach_router
api_router.include_router(founder_outreach_router.router)

View File

@ -156,6 +156,23 @@ class Settings(BaseSettings):
DLQ_MAX_RETRIES: int = 5
DLQ_DRAIN_BATCH_SIZE: int = 10
# ── Local LLM (Ollama) ──────────────────────────────
OLLAMA_BASE_URL: str = "http://localhost:11434/v1"
OLLAMA_MODEL: str = "qwen2.5:7b"
LLM_CACHE_ENABLED: bool = True
LLM_CACHE_TTL: int = 3600
LLM_RATE_LIMIT_RPM: int = 60
# ── Green API (WhatsApp) ────────────────────────────
GREEN_API_INSTANCE_ID: str = ""
GREEN_API_TOKEN: str = ""
# ── Outreach Rate Limits ────────────────────────────
WHATSAPP_DAILY_LIMIT: int = 15
EMAIL_DAILY_LIMIT: int = 50
EMAIL_BATCH_SIZE: int = 10
EMAIL_BATCH_DELAY_MINUTES: int = 90
# ── Rate Limiting ────────────────────────────────────
RATE_LIMIT_PER_MINUTE: int = 60
RATE_LIMIT_PER_HOUR: int = 1000

View File

@ -16,7 +16,14 @@ def _get_db_url() -> str:
break
except FileNotFoundError:
continue
return url or "sqlite+aiosqlite:///./dealix.db"
if not url:
return "sqlite+aiosqlite:///./dealix.db"
# Railway Postgres gives postgres:// but SQLAlchemy needs postgresql+asyncpg://
if url.startswith("postgres://"):
url = url.replace("postgres://", "postgresql+asyncpg://", 1)
elif url.startswith("postgresql://"):
url = url.replace("postgresql://", "postgresql+asyncpg://", 1)
return url
_DB_URL = _get_db_url()

View File

@ -1,29 +1,134 @@
"""Email Sender — SMTP with rate limiting, HTML wrapper, and compliance headers.
Supports Gmail app passwords. Adds professional HTML wrapper for Arabic
RTL emails and List-Unsubscribe header for compliance.
"""
import asyncio
import logging
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional
from app.config import get_settings
logger = logging.getLogger("dealix.email")
settings = get_settings()
UNSUBSCRIBE_AR = "\n\n---\nإذا ما يناسبكم، اكتبوا \"إيقاف\" ولن نتواصل مرة ثانية."
UNSUBSCRIBE_EN = "\n\n---\nTo stop receiving these emails, reply with \"STOP\"."
async def send_email(to_email: str, subject: str, body_html: str, from_name: str = None) -> dict:
"""Send email via SMTP."""
def _wrap_html(body: str, direction: str = "rtl", lang: str = "ar") -> str:
"""Wrap plain text or simple HTML in a professional email template."""
body_html = body.replace("\n", "<br>") if "<" not in body else body
return f"""<!DOCTYPE html>
<html lang="{lang}" dir="{direction}">
<head><meta charset="UTF-8"></head>
<body style="font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 15px;
line-height: 1.7; color: #1a1a1a; max-width: 600px; margin: 0 auto;
padding: 20px; direction: {direction};">
{body_html}
<div style="margin-top: 30px; padding-top: 15px; border-top: 1px solid #e5e5e5;
font-size: 12px; color: #999;">
Dealix dealix.me
</div>
</body></html>"""
async def send_email(
to_email: str,
subject: str,
body_html: str,
from_name: Optional[str] = None,
language: str = "ar",
add_unsubscribe: bool = True,
delay_seconds: float = 0,
) -> dict:
"""Send email via SMTP with compliance headers.
Args:
to_email: Recipient email
subject: Email subject
body_html: Email body (plain text or HTML)
from_name: Sender display name
language: 'ar' or 'en' affects direction and unsubscribe text
add_unsubscribe: Whether to append unsubscribe line
delay_seconds: Wait before sending (for rate limiting in batch)
"""
if not settings.SMTP_USER or not settings.SMTP_PASSWORD:
return {"status": "error", "detail": "Email not configured"}
return {"status": "error", "detail": "SMTP_USER and SMTP_PASSWORD not configured. Add Gmail app password in Railway env."}
if delay_seconds > 0:
await asyncio.sleep(delay_seconds)
if add_unsubscribe:
unsub = UNSUBSCRIBE_AR if language == "ar" else UNSUBSCRIBE_EN
body_html = body_html + unsub
direction = "rtl" if language == "ar" else "ltr"
wrapped = _wrap_html(body_html, direction=direction, lang=language)
sender_name = from_name or settings.EMAIL_FROM_NAME or settings.APP_NAME
from_addr = getattr(settings, "EMAIL_FROM_ADDRESS", settings.SMTP_USER)
sender_name = from_name or settings.APP_NAME
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = f"{sender_name} <{settings.SMTP_USER}>"
msg["From"] = f"{sender_name} <{from_addr}>"
msg["To"] = to_email
msg["List-Unsubscribe"] = f"<mailto:{from_addr}?subject=unsubscribe>"
msg["X-Mailer"] = "Dealix/1.0"
msg.attach(MIMEText(body_html, "html", "utf-8"))
msg.attach(MIMEText(body_html, "plain", "utf-8"))
msg.attach(MIMEText(wrapped, "html", "utf-8"))
try:
with smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT) as server:
server.starttls()
server.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
server.sendmail(settings.SMTP_USER, to_email, msg.as_string())
return {"status": "sent"}
server.sendmail(from_addr, to_email, msg.as_string())
logger.info("Email sent to %s: %s", to_email, subject[:50])
return {"status": "sent", "to": to_email}
except smtplib.SMTPAuthenticationError:
logger.error("SMTP auth failed — check SMTP_USER and SMTP_PASSWORD (Gmail app password)")
return {"status": "error", "detail": "SMTP authentication failed. Use Gmail App Password, not regular password."}
except Exception as e:
return {"status": "error", "detail": str(e)}
logger.error("Email send failed: %s", e)
return {"status": "error", "detail": str(e)[:200]}
async def send_email_batch(
emails: list[dict],
delay_between: float = 2.0,
max_batch: int = 10,
) -> dict:
"""Send a batch of emails with delays between each.
Args:
emails: List of dicts with {to, subject, body, language}
delay_between: Seconds between each email (default 2)
max_batch: Max emails per batch (default 10)
Returns: {sent, failed, results}
"""
sent = 0
failed = 0
results = []
for i, email in enumerate(emails[:max_batch]):
delay = delay_between if i > 0 else 0
result = await send_email(
to_email=email["to"],
subject=email["subject"],
body_html=email["body"],
language=email.get("language", "ar"),
delay_seconds=delay,
)
results.append({"to": email["to"], **result})
if result["status"] == "sent":
sent += 1
else:
failed += 1
return {"sent": sent, "failed": failed, "total": len(results), "results": results}

View File

@ -0,0 +1,77 @@
"""OutreachDraft — DB-persisted draft queue for all outreach channels.
Every generated message starts as status='draft'. Sami reviews and
approves before any send. Approved drafts are dispatched via existing
Celery send tasks.
"""
from __future__ import annotations
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, Optional
from sqlalchemy import Column, String, Integer, Boolean, DateTime, Text, JSON
from sqlalchemy.dialects.postgresql import UUID
try:
from app.database import Base
except ImportError:
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class OutreachDraft(Base):
__tablename__ = "outreach_drafts"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
batch_id = Column(String(64), index=True)
company = Column(String(255), nullable=False)
contact_name = Column(String(255), default="")
contact_email = Column(String(255), default="")
contact_phone = Column(String(32), default="")
channel = Column(String(20), nullable=False) # email | whatsapp | sms | linkedin
subject = Column(String(500), default="")
body = Column(Text, nullable=False)
followup_2d = Column(Text, default="")
followup_5d = Column(Text, default="")
call_script = Column(Text, default="")
sector = Column(String(100), default="")
city = Column(String(100), default="")
pain_hypothesis = Column(Text, default="")
fit_score = Column(Integer, default=0)
risk_score = Column(Integer, default=0)
status = Column(String(20), default="draft", index=True)
# draft | approved | sent | replied | opted_out | bounced | skipped
approval_required = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
approved_at = Column(DateTime(timezone=True), nullable=True)
sent_at = Column(DateTime(timezone=True), nullable=True)
replied_at = Column(DateTime(timezone=True), nullable=True)
reply_text = Column(Text, nullable=True)
reply_category = Column(String(50), nullable=True)
next_action = Column(String(100), nullable=True)
source = Column(String(100), default="daily_pipeline")
metadata_ = Column("metadata", JSON, default=dict)
def to_dict(self) -> Dict[str, Any]:
return {
"id": str(self.id),
"batch_id": self.batch_id,
"company": self.company,
"contact_name": self.contact_name,
"contact_email": self.contact_email,
"channel": self.channel,
"subject": self.subject,
"body": self.body[:200] + "..." if len(self.body or "") > 200 else self.body,
"sector": self.sector,
"city": self.city,
"fit_score": self.fit_score,
"risk_score": self.risk_score,
"status": self.status,
"created_at": self.created_at.isoformat() if self.created_at else None,
"sent_at": self.sent_at.isoformat() if self.sent_at else None,
"reply_category": self.reply_category,
"next_action": self.next_action,
}

View File

@ -0,0 +1,291 @@
"""Dealix Full OS Orchestrator — connects all services into a unified deal lifecycle.
This is the brain that ties WhatsApp brain + sequences + autopilot + CRM + booking
into one autonomous system. Every inbound lead flows through a state machine:
new_lead qualify nurture meeting_booked proposal negotiation closed_won/lost
At each stage, the orchestrator:
1. Detects what happened (inbound message, reply, booking, payment)
2. Classifies intent
3. Decides next action
4. Executes via the appropriate service (WhatsApp, email, CRM, booking)
5. Logs everything
6. Moves to next stage or holds for human approval
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, Optional
from uuid import uuid4
from pydantic import BaseModel
logger = logging.getLogger("dealix.full_os")
class DealStage(str, Enum):
NEW_LEAD = "new_lead"
QUALIFYING = "qualifying"
QUALIFIED = "qualified"
NURTURING = "nurturing"
MEETING_BOOKED = "meeting_booked"
MEETING_DONE = "meeting_done"
PROPOSAL_SENT = "proposal_sent"
NEGOTIATING = "negotiating"
PAYMENT_REQUESTED = "payment_requested"
PILOT_ACTIVE = "pilot_active"
CLOSED_WON = "closed_won"
CLOSED_LOST = "closed_lost"
OPTED_OUT = "opted_out"
class LeadSource(str, Enum):
WHATSAPP_INBOUND = "whatsapp_inbound"
WEBSITE_FORM = "website_form"
LINKEDIN_REPLY = "linkedin_reply"
EMAIL_REPLY = "email_reply"
OUTREACH_REPLY = "outreach_reply"
REFERRAL = "referral"
PARTNER = "partner"
MANUAL = "manual"
class ActionType(str, Enum):
SEND_WHATSAPP = "send_whatsapp"
SEND_EMAIL = "send_email"
SEND_SMS = "send_sms"
BOOK_MEETING = "book_meeting"
SEND_PROPOSAL = "send_proposal"
REQUEST_PAYMENT = "request_payment"
ENROLL_SEQUENCE = "enroll_sequence"
CLASSIFY_REPLY = "classify_reply"
ESCALATE_HUMAN = "escalate_human"
SYNC_CRM = "sync_crm"
LOG_ACTIVITY = "log_activity"
ONBOARD_CUSTOMER = "onboard_customer"
GENERATE_REPORT = "generate_report"
DO_NOTHING = "do_nothing"
STAGE_TRANSITIONS = {
DealStage.NEW_LEAD: {
"interested": DealStage.QUALIFYING,
"ask_price": DealStage.QUALIFYING,
"ask_demo": DealStage.MEETING_BOOKED,
"ask_details": DealStage.QUALIFYING,
"not_now": DealStage.NURTURING,
"unsubscribe": DealStage.OPTED_OUT,
"default": DealStage.QUALIFYING,
},
DealStage.QUALIFYING: {
"qualified": DealStage.QUALIFIED,
"not_qualified": DealStage.NURTURING,
"wants_demo": DealStage.MEETING_BOOKED,
"unsubscribe": DealStage.OPTED_OUT,
"default": DealStage.NURTURING,
},
DealStage.QUALIFIED: {
"meeting_booked": DealStage.MEETING_BOOKED,
"sequence_enrolled": DealStage.NURTURING,
"default": DealStage.NURTURING,
},
DealStage.NURTURING: {
"replied_positive": DealStage.QUALIFYING,
"meeting_booked": DealStage.MEETING_BOOKED,
"unsubscribe": DealStage.OPTED_OUT,
"default": DealStage.NURTURING,
},
DealStage.MEETING_BOOKED: {
"meeting_done": DealStage.MEETING_DONE,
"no_show": DealStage.NURTURING,
"cancelled": DealStage.NURTURING,
"default": DealStage.MEETING_BOOKED,
},
DealStage.MEETING_DONE: {
"proposal_sent": DealStage.PROPOSAL_SENT,
"not_interested": DealStage.CLOSED_LOST,
"needs_time": DealStage.NURTURING,
"default": DealStage.PROPOSAL_SENT,
},
DealStage.PROPOSAL_SENT: {
"accepted": DealStage.PAYMENT_REQUESTED,
"negotiating": DealStage.NEGOTIATING,
"rejected": DealStage.CLOSED_LOST,
"default": DealStage.NEGOTIATING,
},
DealStage.NEGOTIATING: {
"accepted": DealStage.PAYMENT_REQUESTED,
"rejected": DealStage.CLOSED_LOST,
"default": DealStage.NEGOTIATING,
},
DealStage.PAYMENT_REQUESTED: {
"paid": DealStage.PILOT_ACTIVE,
"declined": DealStage.NEGOTIATING,
"default": DealStage.PAYMENT_REQUESTED,
},
DealStage.PILOT_ACTIVE: {
"converted": DealStage.CLOSED_WON,
"churned": DealStage.CLOSED_LOST,
"default": DealStage.PILOT_ACTIVE,
},
}
STAGE_AUTO_ACTIONS = {
DealStage.NEW_LEAD: [
ActionType.CLASSIFY_REPLY,
ActionType.SEND_WHATSAPP,
ActionType.LOG_ACTIVITY,
],
DealStage.QUALIFYING: [
ActionType.SEND_WHATSAPP,
ActionType.LOG_ACTIVITY,
],
DealStage.QUALIFIED: [
ActionType.BOOK_MEETING,
ActionType.ENROLL_SEQUENCE,
ActionType.SYNC_CRM,
],
DealStage.NURTURING: [
ActionType.ENROLL_SEQUENCE,
],
DealStage.MEETING_BOOKED: [
ActionType.SEND_WHATSAPP,
ActionType.SYNC_CRM,
],
DealStage.MEETING_DONE: [
ActionType.SEND_PROPOSAL,
ActionType.LOG_ACTIVITY,
],
DealStage.PROPOSAL_SENT: [
ActionType.LOG_ACTIVITY,
],
DealStage.NEGOTIATING: [
ActionType.CLASSIFY_REPLY,
ActionType.ESCALATE_HUMAN,
],
DealStage.PAYMENT_REQUESTED: [
ActionType.SEND_WHATSAPP,
ActionType.LOG_ACTIVITY,
],
DealStage.PILOT_ACTIVE: [
ActionType.GENERATE_REPORT,
ActionType.ONBOARD_CUSTOMER,
],
DealStage.CLOSED_WON: [
ActionType.SYNC_CRM,
ActionType.ONBOARD_CUSTOMER,
ActionType.GENERATE_REPORT,
],
DealStage.OPTED_OUT: [
ActionType.DO_NOTHING,
],
}
QUALIFICATION_QUESTIONS_AR = [
"كم lead تقريباً تستقبلون شهرياً؟",
"وش أكبر تحدي عندكم في متابعة الاستفسارات؟",
"هل عندكم فريق مبيعات يتابع الـ leads حالياً؟",
"كم ميزانيتكم الشهرية لأدوات المبيعات/التسويق؟",
"متى تبون تبدون لو الحل مناسب؟",
]
STAGE_MESSAGES_AR = {
DealStage.NEW_LEAD: "أهلاً وسهلاً! 👋 أنا مساعد Dealix الذكي. كيف أقدر أساعدك اليوم؟",
DealStage.QUALIFYING: "ممتاز! عشان أفهم احتياجكم بشكل أفضل — {question}",
DealStage.QUALIFIED: "بناءً على اللي ذكرته، Dealix يقدر يساعدكم. تبي نحجز 20 دقيقة demo؟\ncalendly.com/sami-assiri11/dealix-demo",
DealStage.NURTURING: "مرحبا مرة ثانية! عندنا تحديثات جديدة في Dealix ممكن تفيدكم. تبي أشرح؟",
DealStage.MEETING_BOOKED: "تم الحجز! 🎯 بنرسل لك تذكير قبل الموعد. لو تحتاج تغيّر الوقت قلّي.",
DealStage.MEETING_DONE: "شكراً على وقتك اليوم! بناءً على اللي ناقشناه، جهّزت لك عرض pilot مخصص.",
DealStage.PROPOSAL_SENT: "أرسلت لك تفاصيل العرض. أي سؤال أنا موجود.",
DealStage.NEGOTIATING: "فاهم. خلّني أشوف وش أقدر أسوي عشان نوصل لاتفاق يناسب الطرفين.",
DealStage.PAYMENT_REQUESTED: "العرض جاهز! الدفع:\n- تحويل بنكي\n- STC Pay\nبعد التأكيد نبدأ الإعداد خلال 4 ساعات.",
DealStage.PILOT_ACTIVE: "يوم {day} من الـ pilot! هذا تقرير اليوم:\n{report}",
DealStage.CLOSED_WON: "مبروك! 🎉 أهلاً بكم في Dealix. بنبدأ الإعداد الكامل.",
DealStage.OPTED_OUT: "تم. لن نتواصل مرة ثانية. شكراً على وقتكم.",
}
class OrchestratorEvent(BaseModel):
lead_id: str = ""
phone: str = ""
email: str = ""
company: str = ""
sector: str = ""
source: str = "whatsapp_inbound"
message: str = ""
current_stage: str = "new_lead"
event_type: str = "inbound_message"
class FullOSOrchestrator:
"""Central brain that processes events and drives the deal lifecycle."""
def process_event(self, event: OrchestratorEvent) -> Dict[str, Any]:
current = DealStage(event.current_stage) if event.current_stage else DealStage.NEW_LEAD
intent = self._classify_intent(event.message)
transitions = STAGE_TRANSITIONS.get(current, {})
next_stage = transitions.get(intent, transitions.get("default", current))
actions = STAGE_AUTO_ACTIONS.get(next_stage, [])
response_message = self._get_stage_message(next_stage, event)
human_required = next_stage in (
DealStage.NEGOTIATING,
DealStage.PAYMENT_REQUESTED,
DealStage.PILOT_ACTIVE,
)
return {
"lead_id": event.lead_id or str(uuid4())[:8],
"previous_stage": current.value,
"intent_detected": intent,
"new_stage": next_stage.value,
"actions": [a.value for a in actions],
"response_message_ar": response_message,
"human_approval_required": human_required,
"auto_send_allowed": not human_required and next_stage != DealStage.OPTED_OUT,
"timestamp": datetime.now(timezone.utc).isoformat(),
"qualification_question": self._get_next_question(next_stage, event),
}
def _classify_intent(self, message: str) -> str:
if not message:
return "default"
text = message.lower()
if any(w in text for w in ["إيقاف", "stop", "unsubscribe", "لا تتواصل"]):
return "unsubscribe"
if any(w in text for w in ["مهتم", "interested", "أبي أجرب", "نجرب", "تمام", "نعم"]):
return "interested"
if any(w in text for w in ["demo", "ديمو", "أوريني", "عرض", "شرح"]):
return "wants_demo"
if any(w in text for w in ["كم السعر", "كم التكلفة", "pricing", "أسعار"]):
return "ask_price"
if any(w in text for w in ["تفاصيل", "details", "أكثر"]):
return "ask_details"
if any(w in text for w in ["لاحقاً", "later", "مو الحين", "بعدين"]):
return "not_now"
if any(w in text for w in ["دفعت", "حوّلت", "paid", "تم الدفع"]):
return "paid"
if any(w in text for w in ["شراكة", "partner", "وكالة"]):
return "interested"
return "default"
def _get_stage_message(self, stage: DealStage, event: OrchestratorEvent) -> str:
template = STAGE_MESSAGES_AR.get(stage, "")
return template.replace("{company}", event.company or "").replace("{question}", "").replace("{day}", "1").replace("{report}", "")
def _get_next_question(self, stage: DealStage, event: OrchestratorEvent) -> Optional[str]:
if stage == DealStage.QUALIFYING:
return QUALIFICATION_QUESTIONS_AR[0]
return None
orchestrator = FullOSOrchestrator()

View File

@ -0,0 +1,205 @@
"""Multi-Provider WhatsApp Send — tries multiple providers with automatic fallback.
Supported providers (in priority order):
1. Green API free dev tier, simple REST, green-api.com
2. Ultramsg simple REST, ultramsg.com
3. WhatsApp Cloud API (official Meta) needs Business verification
4. Fonnte ultra-cheap, fonnte.com
The system tries each provider in order until one succeeds.
All providers normalize Saudi phone numbers to 966XXXXXXXXX.
"""
from __future__ import annotations
import logging
import os
from typing import Any, Dict, Optional
import httpx
logger = logging.getLogger("dealix.whatsapp_multi")
def _format_saudi_phone(phone: str) -> str:
phone = phone.strip().replace(" ", "").replace("-", "").replace("+", "")
if phone.startswith("05"):
phone = "966" + phone[1:]
elif phone.startswith("00966"):
phone = phone[2:]
elif phone.startswith("966"):
pass
elif phone.startswith("5") and len(phone) == 9:
phone = "966" + phone
return phone
async def send_via_greenapi(phone: str, message: str) -> Dict[str, Any]:
"""Green API — free dev tier, green-api.com.
Env vars: GREEN_API_INSTANCE_ID, GREEN_API_TOKEN
"""
instance = os.getenv("GREEN_API_INSTANCE_ID", "")
token = os.getenv("GREEN_API_TOKEN", "")
if not instance or not token:
return {"provider": "greenapi", "status": "not_configured"}
formatted = _format_saudi_phone(phone)
url = f"https://api.green-api.com/waInstance{instance}/sendMessage/{token}"
payload = {
"chatId": f"{formatted}@c.us",
"message": message,
}
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, json=payload)
data = resp.json()
if resp.status_code == 200 and data.get("idMessage"):
logger.info("GreenAPI sent to %s: %s", formatted, data.get("idMessage"))
return {"provider": "greenapi", "status": "sent", "message_id": data.get("idMessage")}
return {"provider": "greenapi", "status": "failed", "detail": data}
except Exception as exc:
return {"provider": "greenapi", "status": "error", "detail": str(exc)[:200]}
async def send_via_ultramsg(phone: str, message: str) -> Dict[str, Any]:
"""Ultramsg — simple REST API, ultramsg.com.
Env vars: ULTRAMSG_INSTANCE_ID, ULTRAMSG_TOKEN
"""
instance = os.getenv("ULTRAMSG_INSTANCE_ID", "")
token = os.getenv("ULTRAMSG_TOKEN", "")
if not instance or not token:
return {"provider": "ultramsg", "status": "not_configured"}
formatted = _format_saudi_phone(phone)
url = f"https://api.ultramsg.com/{instance}/messages/chat"
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, data={
"token": token,
"to": formatted,
"body": message,
})
data = resp.json()
if "error" not in str(data).lower() or data.get("sent") == "true":
logger.info("Ultramsg sent to %s", formatted)
return {"provider": "ultramsg", "status": "sent", "result": data}
return {"provider": "ultramsg", "status": "failed", "detail": data}
except Exception as exc:
return {"provider": "ultramsg", "status": "error", "detail": str(exc)[:200]}
async def send_via_fonnte(phone: str, message: str) -> Dict[str, Any]:
"""Fonnte — ultra-cheap, fonnte.com.
Env vars: FONNTE_TOKEN
"""
token = os.getenv("FONNTE_TOKEN", "")
if not token:
return {"provider": "fonnte", "status": "not_configured"}
formatted = _format_saudi_phone(phone)
url = "https://api.fonnte.com/send"
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, headers={"Authorization": token}, json={
"target": formatted,
"message": message,
})
data = resp.json()
if data.get("status"):
logger.info("Fonnte sent to %s", formatted)
return {"provider": "fonnte", "status": "sent", "result": data}
return {"provider": "fonnte", "status": "failed", "detail": data}
except Exception as exc:
return {"provider": "fonnte", "status": "error", "detail": str(exc)[:200]}
async def send_via_meta_cloud(phone: str, message: str) -> Dict[str, Any]:
"""Official WhatsApp Cloud API (Meta).
Env vars: WHATSAPP_API_TOKEN, WHATSAPP_PHONE_NUMBER_ID
"""
token = os.getenv("WHATSAPP_API_TOKEN", "")
phone_id = os.getenv("WHATSAPP_PHONE_NUMBER_ID", "")
if not token or not phone_id:
return {"provider": "meta_cloud", "status": "not_configured"}
formatted = _format_saudi_phone(phone)
url = f"https://graph.facebook.com/v21.0/{phone_id}/messages"
try:
async with httpx.AsyncClient(timeout=15.0) as client:
resp = await client.post(url, headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}, json={
"messaging_product": "whatsapp",
"to": formatted,
"type": "text",
"text": {"body": message},
})
data = resp.json()
if resp.status_code == 200 and data.get("messages"):
msg_id = data["messages"][0].get("id", "")
logger.info("MetaCloud sent to %s: %s", formatted, msg_id)
return {"provider": "meta_cloud", "status": "sent", "message_id": msg_id}
return {"provider": "meta_cloud", "status": "failed", "detail": data}
except Exception as exc:
return {"provider": "meta_cloud", "status": "error", "detail": str(exc)[:200]}
PROVIDER_CHAIN = [
("greenapi", send_via_greenapi),
("ultramsg", send_via_ultramsg),
("fonnte", send_via_fonnte),
("meta_cloud", send_via_meta_cloud),
]
async def send_whatsapp_smart(phone: str, message: str) -> Dict[str, Any]:
"""Try all configured providers in order until one succeeds."""
attempts = []
for name, send_fn in PROVIDER_CHAIN:
result = await send_fn(phone, message)
attempts.append(result)
if result.get("status") == "sent":
return {
"sent": True,
"provider_used": name,
"result": result,
"attempts": len(attempts),
}
return {
"sent": False,
"provider_used": None,
"error": "all_providers_failed_or_not_configured",
"attempts": attempts,
}
async def check_providers() -> Dict[str, Any]:
"""Check which providers are configured (without sending)."""
status = {}
for name, _ in PROVIDER_CHAIN:
if name == "greenapi":
configured = bool(os.getenv("GREEN_API_INSTANCE_ID") and os.getenv("GREEN_API_TOKEN"))
elif name == "ultramsg":
configured = bool(os.getenv("ULTRAMSG_INSTANCE_ID") and os.getenv("ULTRAMSG_TOKEN"))
elif name == "fonnte":
configured = bool(os.getenv("FONNTE_TOKEN"))
elif name == "meta_cloud":
configured = bool(os.getenv("WHATSAPP_API_TOKEN") and os.getenv("WHATSAPP_PHONE_NUMBER_ID"))
else:
configured = False
status[name] = configured
return {
"providers": status,
"any_configured": any(status.values()),
"recommended": "greenapi" if not any(status.values()) else next((k for k, v in status.items() if v), None),
}

View File

@ -2,8 +2,7 @@
dockerfilePath = "Dockerfile"
[deploy]
healthcheckPath = "/api/v1/health"
healthcheckPath = "/health"
healthcheckTimeout = 120
startCommand = "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000} --workers 2"
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3

View File

@ -83,3 +83,4 @@ qrcode==8.0
Pillow==11.0.0
xmltodict==0.14.2
email-validator>=2.1.0
aiosqlite>=0.20.0

16
salesflow-saas/backend/start.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
set -e
echo "[dealix] PORT=$PORT"
echo "[dealix] Testing imports..."
python3 -c "
try:
from app.main import app
print(f'[dealix] OK — {len(app.routes)} routes')
except Exception as e:
print(f'[dealix] IMPORT FAILED: {e}')
import traceback
traceback.print_exc()
exit(1)
" 2>&1
echo "[dealix] Starting uvicorn on port ${PORT:-8000}..."
exec uvicorn app.main:app --host 0.0.0.0 --port "${PORT:-8000}" --workers 1 --log-level info

View File

@ -0,0 +1,175 @@
"""Tests for the full automation outreach system — drafts, pipeline, followups."""
import pytest
from fastapi.testclient import TestClient
from fastapi import FastAPI
@pytest.fixture
def app():
app = FastAPI()
from app.api.v1.automation import router as auto_router
app.include_router(auto_router)
return app
@pytest.fixture
def client(app):
return TestClient(app)
def test_email_generate(client):
resp = client.post("/automation/email/generate", json={
"company": "Foodics",
"sector": "saas",
"city": "الرياض",
"contact_name": "أحمد",
})
assert resp.status_code == 200
data = resp.json()
assert "subject_ar" in data
assert "body_ar" in data
assert "followup_day_2" in data
assert "followup_day_5" in data
assert "call_script_ar" in data
assert "linkedin_manual_message" in data
assert data["opt_out_included"] is True
assert data["word_count"] > 0
assert "Foodics" in data["subject_ar"]
def test_email_generate_with_signals(client):
resp = client.post("/automation/email/generate", json={
"company": "TestCo",
"sector": "real_estate",
"signals": ["hubspot"],
})
data = resp.json()
assert "HubSpot" in data["body_ar"]
def test_compliance_check_allowed(client):
resp = client.post("/automation/compliance/check", json={
"email": "ahmed@company.sa",
"company": "TestCo",
"source": "linkedin",
})
data = resp.json()
assert data["allowed"] is True
assert data["reason"] == "compliant"
def test_compliance_check_opt_out(client):
resp = client.post("/automation/compliance/check", json={
"email": "ahmed@company.sa",
"opt_out": True,
})
data = resp.json()
assert data["allowed"] is False
assert data["reason"] == "opt_out"
def test_compliance_check_bounced(client):
resp = client.post("/automation/compliance/check", json={
"email": "bad@company.sa",
"bounced_before": True,
})
data = resp.json()
assert data["allowed"] is False
assert data["reason"] == "bounced_before"
def test_compliance_check_high_risk(client):
resp = client.post("/automation/compliance/check", json={
"email": "ceo@big.sa",
"risk_score": 80,
})
data = resp.json()
assert data["allowed"] is False
assert data["reason"] == "high_risk"
def test_compliance_check_personal_email(client):
resp = client.post("/automation/compliance/check", json={
"email": "ahmed@gmail.com",
"source": "linkedin",
})
data = resp.json()
assert data["allowed"] is True
assert "personal_email" in data["reason"]
def test_compliance_check_no_source(client):
resp = client.post("/automation/compliance/check", json={
"email": "ahmed@company.sa",
"source": "",
})
data = resp.json()
assert data["allowed"] is False
assert data["reason"] == "no_source"
def test_reply_classify_interested(client):
resp = client.post("/automation/reply/classify", json={
"reply_text": "مهتم جداً، أبي أجرب",
"company": "TestCo",
})
data = resp.json()
assert data["category"] == "interested"
assert data["auto_reply_allowed"] is True
assert "calendly" in data["suggested_response"].lower() or "demo" in data["suggested_response"].lower() or "20 دقيقة" in data["suggested_response"]
def test_reply_classify_price(client):
resp = client.post("/automation/reply/classify", json={
"reply_text": "كم السعر؟",
})
data = resp.json()
assert data["category"] == "ask_price"
assert "499" in data["suggested_response"]
def test_reply_classify_unsubscribe(client):
resp = client.post("/automation/reply/classify", json={
"reply_text": "إيقاف لا تتواصل معي",
})
data = resp.json()
assert data["category"] == "unsubscribe"
assert data["auto_reply_allowed"] is False
def test_reply_classify_crm(client):
resp = client.post("/automation/reply/classify", json={
"reply_text": "عندنا CRM وما نحتاج نظام ثاني",
})
data = resp.json()
assert data["category"] == "already_has_crm"
assert "طبقة" in data["suggested_response"] or "CRM" in data["suggested_response"]
def test_daily_targeting_generate(client):
resp = client.post("/automation/daily-targeting/generate", json={
"sectors": ["real_estate", "construction"],
"cities": ["الرياض"],
"daily_target_count": 5,
})
data = resp.json()
assert data["total_generated"] > 0
assert len(data["targets"]) <= 5
assert data["targets"][0]["sector"] in ("real_estate", "construction")
assert data["approval_required"] is True
def test_sector_pain_map_coverage(client):
"""Verify all 9 sectors produce valid emails."""
sectors = ["real_estate", "construction", "hospitality", "food_beverage",
"logistics", "agency", "saas", "healthcare", "education"]
for sector in sectors:
resp = client.post("/automation/email/generate", json={
"company": f"Test_{sector}",
"sector": sector,
})
assert resp.status_code == 200, f"Failed for sector: {sector}"
data = resp.json()
assert len(data["body_ar"]) > 50, f"Empty body for {sector}"
assert "إيقاف" in data["body_ar"], f"Missing opt-out for {sector}"

View File

@ -0,0 +1,236 @@
# Dealix — ماكينة 10 زبائن/أسبوع
## الحساب الدقيق
```
10 زبائن مدفوعين/أسبوع
= 30 demo/أسبوع (33% demo→paid)
= 100 رد إيجابي/أسبوع (30% reply→demo)
= 500 محادثة/أسبوع (20% touch→reply)
= ~70 محادثة/يوم
```
## مصادر الـ 70 محادثة يومياً
| القناة | يومياً | أسبوعياً | النوع |
|--------|--------|----------|-------|
| LinkedIn DM (مباشر) | 10 | 70 | cold/warm |
| Cold Email (مخصص) | 20 | 140 | cold |
| WhatsApp warm | 5 | 35 | warm |
| Landing page inbound | 5 | 35 | inbound |
| Partner referrals | 5 | 35 | warm |
| X/LinkedIn content inbound | 5 | 35 | inbound |
| Community/group posts | 5 | 35 | warm |
| Referral asks | 5 | 35 | warm |
| Re-engagement (cold leads) | 10 | 70 | reactivation |
| **المجموع** | **70** | **490** | |
## خطة الأسبوع الأول (واقعية)
| اليوم | LinkedIn | Email | WhatsApp | Content | Referral | المجموع |
|-------|----------|-------|----------|---------|----------|---------|
| السبت | 10 | 20 | 5 | 2 | 3 | 40 |
| الأحد | 10 | 20 | 5 | 1 | 3 | 39 |
| الإثنين | 10 | 20 | 5 | 2 | 3 | 40 |
| الثلاثاء | 10 | 20 | 5 | 1 | 3 | 39 |
| الأربعاء | 10 | 20 | 5 | 2 | 3 | 40 |
| الخميس | 10 | 20 | 5 | 1 | 3 | 39 |
| الجمعة | 5 | 0 | 5 | 2 | 5 | 17 |
| **المجموع** | **65** | **120** | **35** | **11** | **23** | **254** |
Week 1 = 254 touch. Week 2+ = 490+/week مع content inbound يكبر.
---
## القطاعات المستهدفة (6 segments)
### Segment 1: SaaS سعودي (أعلى fit)
- **ICP:** شركة SaaS 20-200 موظف، عندها leads من موقع/إعلانات
- **المشكلة:** رد بطيء، leads تبرد، فريق مبيعات صغير
- **العرض:** pilot 7 أيام مجاني → 990 ريال/شهر
- **أمثلة:** Foodics, Salla, Lean, Lucidya, Rewaa, Qoyod, Maqsam
### Segment 2: وكالات تسويق (أعلى volume)
- **ICP:** وكالة 5-50 موظف، عندها عملاء B2B
- **المشكلة:** عملاءهم يجيبون leads بالإعلانات لكن المتابعة ضعيفة
- **العرض:** شراكة setup + retainer (3,000-15,000 لكل عميل)
- **أمثلة:** Peak Content, Digital8, Brand Lounge, Wavy, Bold
### Segment 3: عقارات (أعلى urgency)
- **ICP:** شركة عقارية أو مطوّر، leads كثيرة من إعلانات
- **المشكلة:** 500+ lead/شهر، 30% فقط يُتابع
- **العرض:** pilot على 50 lead → يثبت ROI فوري
- **أمثلة:** Aqar, Dar Global, Roshn
### Segment 4: تجارة إلكترونية (volume)
- **ICP:** متجر إلكتروني متوسط، واتساب كقناة رئيسية
- **المشكلة:** رسائل واتساب كثيرة، رد يدوي بطيء
- **العرض:** رد تلقائي + تأهيل + حجز
- **أمثلة:** Jarir online, Dokkan Afkar, Haraj sellers
### Segment 5: خدمات B2B (high LTV)
- **ICP:** شركة خدمات (استشارات، محاماة، محاسبة)
- **المشكلة:** leads من الموقع تضيع بسبب عدم المتابعة
- **العرض:** pilot مع تأهيل + حجز مواعيد
- **أمثلة:** Big 4 partners, law firms, consulting boutiques
### Segment 6: مطاعم/F&B (quick win)
- **ICP:** سلسلة 5+ فروع، طلبات واتساب/إنستقرام
- **المشكلة:** طلبات B2B (تموين، حفلات) تضيع
- **العرض:** رد تلقائي على طلبات التموين
- **أمثلة:** Albaik (B2B), Herfy catering, Floward corporate
---
## الرسائل حسب القطاع
### SaaS — LinkedIn DM
```
السلام عليكم [الاسم]،
شفت إن [الشركة] تستخدمون [HubSpot/WhatsApp widget/forms]
— يعني عندكم leads تجي من الموقع.
سؤال سريع: كم lead تخسرونه شهرياً بسبب تأخر الرد؟
ديلكس يرد بالعربي خلال 45 ثانية، يؤهل العميل، ويحجز
الموعد تلقائياً. بدون ما تغيّرون CRM.
أوريك تجربة 20 دقيقة على سيناريو من شغلكم؟
calendly.com/sami-assiri11/dealix-demo
```
### وكالات — LinkedIn DM
```
السلام عليكم [الاسم]،
أطلقنا Dealix — طبقة AI بالعربي ترد على leads عملاء الوكالات
وتأهلهم وتحجز لهم مواعيد.
الفكرة للوكالة: تبيعونها كـ setup + retainer فوق حملاتكم
الحالية. العميل يشوف ROI أسرع، وأنتم تزيدون MRR.
أبي أجرب مع أول عميل عندكم كـ pilot مجاني.
20 دقيقة أشرح الآلية؟
calendly.com/sami-assiri11/dealix-demo
```
### عقارات — WhatsApp
```
السلام عليكم [الاسم]،
أشتغل على Dealix — يرد على leads العقار بالعربي
خلال 45 ثانية، يسأل عن الميزانية والموقع المفضل
والمساحة، ويحجز موعد معاينة تلقائي.
عندكم leads من إعلانات تبغون تجربون عليها أسبوع؟
بدون التزام.
```
### تجارة إلكترونية — Email
```
الموضوع: رد تلقائي بالعربي على رسائل واتساب متجركم
مرحباً [الاسم]،
Dealix يساعد المتاجر الإلكترونية ترد على استفسارات
واتساب بالعربي تلقائياً — يجاوب عن المنتج، يأكد
التوفر، ويوجّه العميل للشراء.
أقدر أوريك تجربة على 10 محادثات من متجركم.
هل يناسبك هذا الأسبوع؟
إذا ما كان مناسبًا، أرسل "إيقاف" ولن أتابع.
```
---
## الأدوات المطلوبة
| الأداة | الغرض | التكلفة | الأولوية |
|--------|--------|---------|---------|
| LinkedIn Sales Nav | بحث + 50 InMail/شهر | $80/شهر | P0 |
| Instantly.ai أو Smartlead | cold email automation | $30/شهر | P0 |
| Calendly | حجز مواعيد | مجاني | P0 (موجود) |
| WhatsApp Business | رسائل warm | مجاني | P0 |
| Resend API | transactional email | مجاني | P0 (موجود) |
| Google Sheets | CRM يدوي | مجاني | P0 |
| PostHog | tracking | مجاني | P0 (موجود) |
| Stripe/Moyasar | دفع | بعد أول demo | P1 |
**إجمالي: $110/شهر** — أقل من ربع زبون واحد.
---
## Tracker Template (Google Sheet)
| # | الشركة | الشخص | القطاع | القناة | التاريخ | الحالة | الرد | Demo | Pilot | Paid | ملاحظات |
|---|--------|-------|--------|--------|---------|--------|------|------|-------|------|---------|
| 1 | | | | LinkedIn | | sent | | | | | |
| 2 | | | | Email | | sent | | | | | |
**الحالات:** new → sent → replied → demo_booked → demo_done → pilot_started → pilot_ended → paid → churned
---
## KPIs أسبوعية
| المقياس | الأسبوع 1 | الأسبوع 2 | الأسبوع 3 | الأسبوع 4 | الهدف الشهري |
|---------|-----------|-----------|-----------|-----------|-------------|
| Touches | 254 | 490 | 490 | 490 | 1,724 |
| Replies | 25 | 50 | 70 | 100 | 245 |
| Demos | 3 | 8 | 10 | 10 | 31 |
| Pilots | 1 | 3 | 5 | 5 | 14 |
| Paid | 0 | 1 | 3 | 6 | **10** |
| MRR (SAR) | 0 | 990 | 2,970 | 5,940 | **9,900** |
---
## Daily Operating Schedule
```
07:00 Wake + check replies from yesterday
08:00 LinkedIn: send 10 DMs (from target list)
09:00 Email: send 20 cold emails (Instantly/Smartlead)
10:00 WhatsApp: 5 warm messages
10:30 Demo #1 (if booked)
11:30 Demo #2 (if booked)
12:00 Follow-ups: reply to all messages
13:00 Break
14:00 Partner outreach: 5 agency messages
15:00 Demo #3 (if booked)
16:00 Content: write + publish 1-2 posts
17:00 Referral asks: 3-5 warm intros
18:00 Scorecard update + tomorrow prep
20:00 Reply to late messages
```
---
## Week 1 Exact Plan
### Day 1 (السبت)
- [ ] Morning: send 10 LinkedIn DMs (SaaS segment)
- [ ] Send 20 cold emails (SaaS + agencies)
- [ ] 5 WhatsApp to warm contacts
- [ ] Publish Founder Launch Post
- [ ] 3 referral asks
- [ ] Scorecard update
### Day 2 (الأحد)
- [ ] 10 LinkedIn DMs (agencies segment)
- [ ] 20 emails (agencies + real estate)
- [ ] 5 WhatsApp
- [ ] 1 X post
- [ ] Handle Day 1 replies
- [ ] 3 referral asks
### Day 3 (الإثنين)
- [ ] 10 LinkedIn DMs (real estate + ecomm)
- [ ] 20 emails
- [ ] 5 WhatsApp
- [ ] LinkedIn article/thread
- [ ] First demos (if booked)
- [ ] 3 referral asks
### Day 4-7: repeat + handle demos + send pilot offers + request payments

View File

@ -0,0 +1,212 @@
# Dealix — Agency & Partner Offer
---
## Who This Is For
| Partner Type | Profile | Why They Care |
|-------------|---------|---------------|
| **Performance Agency** | Runs ads for Saudi B2B clients, 5-30 people | Leads go cold → client churns → agency loses MRR |
| **Full-Service Digital Agency** | Websites + ads + social, 10-50 people | Needs post-lead solution to retain clients |
| **Freelance Digital Marketer** | Solo or 2-3 people, manages multiple clients | Wants recurring revenue beyond project fees |
| **CRM/RevOps Consultant** | Implements HubSpot/Zoho/Salesforce | Client doesn't use CRM because no leads flow in properly |
| **WhatsApp Marketing Specialist** | Knows Saudi WhatsApp culture | Can't scale manual responses for 10+ clients |
| **IT Services Company** | Sells tech solutions to Saudi SMEs | Wants to add SaaS reselling to portfolio |
---
## What Partners Can Sell
### Tier 1: Dealix Response (للشركات الصغيرة)
**Client pays partner:** 990 SAR/month
**Setup fee:** 3,000 SAR (partner keeps 100%)
**Partner MRR share:** 30% = 297 SAR/month
**Scope:**
- AI Arabic responder on WhatsApp or web forms
- 5 qualification questions configured per client
- Leads classified: hot / warm / cold
- Weekly summary report (manual first, auto later)
**Dealix delivers:** System config + AI response + weekly report
**Partner delivers:** Client relationship + billing + communication
### Tier 2: Dealix Growth (للشركات المتوسطة)
**Client pays partner:** 2,490 SAR/month
**Setup fee:** 7,000 SAR (partner keeps 100%)
**Partner MRR share:** 25% = 622 SAR/month
**Scope:**
Everything in Tier 1, plus:
- Calendly/booking integration
- CRM sync (HubSpot two-way)
- Follow-up sequences (Day +2, +5, +10)
- Daily report (not just weekly)
### Tier 3: Dealix Enterprise (للمؤسسات)
**Client pays partner:** Custom (starting 5,000+ SAR/month)
**Setup fee:** 15,000+ SAR
**Partner MRR share:** 20% (negotiable)
**Scope:**
Everything in Tier 2, plus:
- Multi-channel (WhatsApp + Email + Voice)
- Executive dashboard
- Approval workflows
- SLA tracking
- Dedicated account manager from Dealix
---
## Service-Exchange Models
### Model A: Referral Only
- Partner introduces client
- Dealix handles everything (sales, setup, billing)
- Partner earns **10% of MRR for 12 months**
- No work required from partner after intro
- Best for: consultants, mentors, advisors
### Model B: Co-Sell
- Partner sells to client, collects payment
- Dealix provides system + support
- Partner earns **setup fee (100%) + 20-30% MRR**
- Partner handles client relationship
- Best for: agencies with existing clients
### Model C: White-Label (future)
- Partner brands Dealix as their own
- Full control of client billing
- Higher MRR share (40-50%)
- Requires Scale plan or above
- **Not available now — post first 10 paying partners**
---
## What Dealix Delivers (manual-first)
| Deliverable | Now (Manual) | Later (Automated) |
|-------------|-------------|------------------|
| Client onboarding | Sami configures in 24h | Self-service portal |
| Arabic response templates | Manually tuned per sector | LLM-generated per client |
| Qualification questions | Configured per client brief | AI-optimized from conversations |
| Daily/weekly reports | Manual email | Auto-generated dashboard |
| CRM sync | Manual export + import | Real-time HubSpot sync |
| Billing to client | Partner handles | Moyasar recurring (future) |
| Partner commission | Manual bank transfer | Automatic monthly payout |
| Escalation | WhatsApp to Sami | Ticketing system |
---
## Implementation Packages
### Quick Start (1 day)
- 1 channel (WhatsApp or web form)
- 5 qualification questions
- 1 sector template
- Weekly report
- **Setup: 3,000 SAR**
### Standard (3 days)
- 2 channels
- Custom qualification flow
- Calendly integration
- CRM sync
- Daily report
- **Setup: 7,000 SAR**
### Advanced (5-7 days)
- All channels
- Multi-department routing
- Approval workflows
- Executive dashboard
- **Setup: 15,000+ SAR**
---
## Referral / Partner Workflow
### Step 1: Partner Applies
- Books Calendly call or sends WhatsApp message
- Sami evaluates fit (are they in Saudi B2B? do they have clients?)
- If yes → simple 1-page partner agreement
### Step 2: First Client Pilot
- Partner introduces their best-fit client
- Dealix runs 7-day pilot on client's leads — **free for first client**
- Client sees: faster response, more qualified leads, booked meetings
### Step 3: Client Converts
- Client agrees to monthly plan
- Partner bills client directly (co-sell) or Dealix bills (referral)
- Partner earns according to model A, B, or C
### Step 4: Scale
- Partner adds more clients
- Each new client = new setup fee + new MRR
- Monthly partner check-in with Sami
---
## Stage-1 Partner Motion (Week 1-4)
### Target: 5 partners signed, 3 with first client in pilot
**Week 1:** Contact 10 agencies
- 5 via LinkedIn DM (message in COMMAND_CENTER.md)
- 5 via warm WhatsApp (personal network)
- Offer: free pilot for first client
**Week 2:** Run 3 pilots
- Each pilot = 7 days on client's leads
- Daily report to partner + client
- Measure: response time, qualified leads, booked meetings
**Week 3:** Convert 2 pilots to paid
- Partner earns first setup fee
- MRR starts flowing
**Week 4:** Onboard 2 more partners
- Use first partner as reference
- Repeat Week 1-2 process
### First 5 Partner Targets
1. **Peak Content** (Riyadh) — content marketing, has B2B clients
2. **Digital8** (Riyadh) — full service, performance marketing
3. **Wavy** (Saudi) — social media management
4. **Hayan** (Saudi) — HubSpot implementation partner
5. **Bold Agency** (Riyadh) — performance + creative
### Partner Outreach Message
```
السلام عليكم [الاسم]،
أنا سامي — مؤسس Dealix. نشتغل على طبقة AI بالعربي ترد
على leads عملاء الوكالات وتأهلهم وتحجز مواعيد تلقائياً.
الفكرة لكم: تبيعونها كـ setup + retainer فوق خدماتكم.
العميل يشوف ROI أسرع — وأنتم تزيدون MRR.
نبدأ بـ pilot مجاني لأول عميل عندكم — 7 أيام بلا تكلفة.
يناسبك 20 دقيقة هذا الأسبوع؟
calendly.com/sami-assiri11/dealix-demo
```
---
## Revenue Math
```
Per partner (conservative):
2 clients/quarter × 990 SAR/month = 1,980 SAR MRR
+ 2 × 3,000 SAR setup = 6,000 SAR
Annual from 1 partner: 23,760 + 6,000 = 29,760 SAR
5 partners:
= 9,900 SAR MRR + 30,000 SAR setup
= ~148,800 SAR/year
10 partners:
= ~297,600 SAR/year
```

View File

@ -0,0 +1,78 @@
# Dealix — Agency / Partner Motion Plan
## Who Are Partners
| Type | Example | What They Sell | Our Revenue Share |
|------|---------|---------------|------------------|
| **Referral** | Freelancer, consultant | Intro only | 10% MRR × 12 months |
| **Implementation** | HubSpot consultant, RevOps | Setup + config | 100% of setup fee |
| **Agency** | Digital agency, performance agency | Setup + monthly service | Setup fee + 20-30% MRR |
| **Strategic** | Salla, Zid, Unifonic | Co-sell / bundle | Revenue share negotiated |
## What Partners Can Sell
### Package 1: "Dealix Basic" (for agency clients)
- AI Arabic responder on WhatsApp/forms
- Lead qualification (5 questions)
- Daily summary report
- **Client pays agency:** 990 SAR/month
- **Agency keeps:** 297 SAR/month (30%)
- **Setup fee to agency:** 3,000 SAR (agency keeps 100%)
### Package 2: "Dealix Growth" (for larger clients)
- Everything in Basic
- Calendly/booking integration
- CRM sync (HubSpot)
- Follow-up sequences
- **Client pays agency:** 2,490 SAR/month
- **Agency keeps:** 622 SAR/month (25%)
- **Setup fee:** 7,000 SAR (agency keeps 100%)
## What We Deliver (manual-first)
- System configuration for each client
- Arabic response templates tuned to client's sector
- Daily monitoring first 7 days
- Escalation support
- Monthly performance review
## What Is Manual Now vs Automated Later
| Activity | Now (Manual) | Later (Automated) |
|----------|-------------|------------------|
| Client onboarding | Sami + Google Sheet | Self-service portal |
| Response config | Manually set templates | LLM-generated per client |
| Daily report | Manual email | Auto-generated dashboard |
| Billing | Bank transfer / STC Pay | Moyasar recurring |
| Partner commission | Manual calculation | Automatic payout |
## Stage-1 Service Exchange (First 3 Partners)
### Offer to Agency:
"أعطيني أول عميل عندك — أسوي له pilot مجاني 7 أيام.
لو العميل رضي واشترك، تبدأ تحصّل 30% من MRR + setup fee كامل لك.
لو ما رضي — لا التزام."
### Partner Onboarding Checklist:
1. Partner signs simple agreement (1 page)
2. Partner introduces first client
3. We run 7-day pilot on client's leads
4. Client converts → partner earns
5. Repeat
## First 5 Partner Targets
1. Peak Content (Riyadh) — content + digital
2. Digital8 (Riyadh) — full service digital
3. Wavy (Saudi) — social media
4. Hayan (Saudi) — HubSpot partner
5. Bold Agency (Riyadh) — performance
## Revenue Math (per partner)
```
1 partner × 3 clients/quarter
= 3 × 990 SAR/month = 2,970 SAR MRR per partner
+ 3 × 3,000 SAR setup = 9,000 SAR one-time
5 partners × same rate = 14,850 SAR MRR + 45,000 SAR setup
= ~223,800 SAR/year from partner channel alone
```

View File

@ -0,0 +1,215 @@
# Dealix — Daily Revenue Machine
# ماكينة الإيراد اليومي
## الهدف: تدفق مالي يومي مستقر
```
الهدف الشهري: 30,000 ريال MRR
= 30 عميل × 990 ريال/شهر (Starter)
= ~1,000 ريال/يوم إيراد متكرر
```
---
## 8 قنوات إيراد متوازية
### القناة 1: LinkedIn Outbound (أعلى جودة)
**الأداة:** LinkedIn Sales Navigator ($80/شهر)
**الحجم:** 10 رسائل/يوم × 6 أيام = 60/أسبوع
**التحويل المتوقع:** 15% رد → 5% demo → 2% paid
**الإيراد المتوقع:** 60 × 2% = ~1 عميل/أسبوع = 990 ريال
**التنفيذ:**
1. Sales Navigator → فلتر: Saudi Arabia + 20-200 employees + SaaS/Services/Real Estate
2. ابحث عن: VP Sales, Head of Sales, CEO, COO, Growth Lead
3. أرسل Connection Request + Note (النص في SAUDI_60_TARGETS.md)
4. بعد القبول → رسالة تفصيلية + Calendly
5. Follow-up +2/+5/+10 أيام
---
### القناة 2: Cold Email (أعلى volume)
**الأداة:** Instantly.ai ($30/شهر) أو Smartlead ($39/شهر)
**الحجم:** 30-50 إيميل/يوم × 6 أيام = 180-300/أسبوع
**التحويل المتوقع:** 3% reply → 1% demo → 0.3% paid
**الإيراد المتوقع:** 300 × 0.3% = ~1 عميل/أسبوع = 990 ريال
**التنفيذ:**
1. اجمع إيميلات من:
- LinkedIn profiles (اسأل في الرسالة)
- Company websites (contact/about pages)
- Apollo.io free tier (50 credits/شهر)
- Hunter.io (50 searches مجاناً)
2. حمّل في Instantly.ai
3. Sequence: 3 إيميلات × 3 أيام بينهم
4. Subject lines مُختبرة:
- "سؤال سريع عن متابعة الـ leads في [الشركة]"
- "73% من العملاء السعوديين يتوقعون رد خلال 30 دقيقة"
- "[الاسم] — فكرة لـ [الشركة]"
5. **مهم:** كل إيميل فيه سطر opt-out: "إذا ما كان مناسبًا، أرسل 'إيقاف'"
---
### القناة 3: WhatsApp Warm (أسرع إقفال)
**الأداة:** WhatsApp Business (مجاني)
**الحجم:** 5-10 رسائل/يوم (warm contacts فقط)
**التحويل المتوقع:** 30% reply → 15% demo → 8% paid
**الإيراد المتوقع:** 50 × 8% = ~4 عملاء/أسبوع = 3,960 ريال
**التنفيذ:**
- أرسل لـ: أصدقاء founders، زملاء سابقين، معارف من مؤتمرات
- الرسالة: Tier 1 من FIRST_REVENUE_GUARANTEE_PLAYBOOK
- **هذي أسرع قناة للإيراد — ابدأ فيها أول**
---
### القناة 4: Agency Partners (أعلى LTV)
**الأداة:** LinkedIn + WhatsApp
**الحجم:** 5 وكالات/أسبوع
**التحويل المتوقع:** 20% partner → 2 عملاء لكل وكالة
**الإيراد المتوقع:** 1 وكالة = 5,000 setup + 990×2 = 6,980 ريال
**التنفيذ:**
1. ابحث: "digital marketing agency Saudi" أو "وكالة تسويق رقمي الرياض"
2. أرسل رسالة وكالة من SAUDI_60_TARGETS.md
3. العرض: Setup 3,000-5,000 + 20-30% MRR recurring
4. أول عميل مجاني كـ proof
---
### القناة 5: Content Inbound (مجاني + مركّب)
**الأداة:** LinkedIn + X (مجاني)
**الحجم:** 1-2 post/يوم
**التحويل المتوقع:** 5 inbound leads/أسبوع بعد أسبوعين
**الإيراد المتوقع:** 5 × 10% = 0.5 عميل/أسبوع بعد شهر
**التنفيذ:**
- **يوم 1:** "أطلقنا Dealix — مندوب مبيعات AI بالعربي"
- **يوم 2:** "73% من العملاء السعوديين يتوقعون رد خلال 30 دقيقة"
- **يوم 3:** "كيف تحوّل وكالتك إلى revenue machine"
- **يوم 4:** "3 أيام من الإطلاق — وش تعلمنا"
- **يوم 5:** "عرض pilot بريال واحد — أول 5 شركات"
- **يوم 6:** "ليش الوكالات هي مستقبل SaaS في السعودية"
- **يوم 7:** "تقرير: Saudi AI GTM 2026"
---
### القناة 6: Community/Group Posts (warm leads)
**الأداة:** WhatsApp/Telegram groups + Reddit r/saudiarabia + LinkedIn groups
**الحجم:** 3-5 posts/أسبوع في مجموعات مختلفة
**التحويل المتوقع:** 2-3 leads/أسبوع
**التنفيذ:**
- ابحث عن مجموعات: "رواد أعمال سعوديين"، "Saudi Startups"، "SaaS MENA"
- انشر قيمة أولاً (مشكلة + حل) — لا تبيع مباشرة
- مثال: "سؤال للمؤسسين: كيف تتعاملون مع leads تجي بالليل؟ نشتغل على حل AI بالعربي — أبي أسمع تجاربكم"
---
### القناة 7: Referral Program (أقل جهد)
**الأداة:** رسالة واتساب + tracking يدوي
**الحجم:** 5 referral asks/أسبوع
**التحويل المتوقع:** 20% يعطون intro
**التنفيذ:**
- بعد كل محادثة إيجابية (حتى لو ما شرى): "تعرف أحد ممكن يستفيد من ديلكس؟ 10% من أول سنة لك"
- رسالة Referral:
"هل تعرف founder سعودي عنده leads كثيرة ومتابعة ضعيفة؟ أعطني اسمه وأنا أتواصل معه. لو اشترك = 10% من MRR لك لمدة سنة."
---
### القناة 8: Paid Ads (بعد إثبات المنتج)
**متى تبدأ:** بعد أول 5 عملاء مدفوعين
**الأداة:** LinkedIn Ads + Google Ads
**الميزانية:** 2,000-5,000 ريال/شهر
**المتوقع:** 10-20 lead/أسبوع × 5% = 1 عميل/أسبوع
---
## الجدول اليومي للإيراد
```
07:00 قراءة الردود + تصنيف (reply classifier)
08:00 LinkedIn: 10 DMs (Sales Navigator)
09:00 Email: 30 cold emails (Instantly.ai)
10:00 WhatsApp: 5 warm messages
10:30 Demo #1
11:30 Demo #2
12:00 Follow-ups: كل الردود
13:00 استراحة
14:00 Partner outreach: 3 وكالات
15:00 Demo #3
16:00 Content: كتابة + نشر 1-2 posts
17:00 Community + Referral asks
18:00 Scorecard update + تحضير الغد
20:00 ردود متأخرة
```
---
## Conversion Funnel المستهدف
```
Week 1: 254 touch → 25 reply → 3 demo → 1 pilot → 0 paid
Week 2: 490 touch → 50 reply → 8 demo → 3 pilot → 1 paid (990 SAR)
Week 3: 490 touch → 70 reply → 10 demo → 5 pilot → 3 paid (2,970 SAR)
Week 4: 490 touch → 100 reply → 10 demo → 5 pilot → 6 paid (5,940 SAR)
Month 2: target 30 paid = 29,700 SAR MRR
Month 3: target 60 paid + 3 agencies = 59,400 + 15,000 = 74,400 SAR
```
---
## الأدوات المطلوبة (إجمالي: 149 ريال/شهر)
| الأداة | الغرض | التكلفة |
|--------|--------|---------|
| LinkedIn Sales Navigator | بحث + InMail | $80 = 300 ريال |
| Instantly.ai | cold email automation | $30 = 112 ريال |
| Calendly | حجز مواعيد | مجاني |
| WhatsApp Business | warm outreach | مجاني |
| Google Sheets | CRM يدوي | مجاني |
| Loom | تسجيل demo | مجاني |
| PostHog | analytics | مجاني |
| Hunter.io | email finder | مجاني (50/شهر) |
---
## Pricing Matrix
| الخطة | السعر | المناسب لـ |
|-------|-------|-----------|
| Pilot | 1 ريال / 7 أيام | إثبات القيمة |
| Starter | 990 ريال/شهر | شركات صغيرة (500 lead/شهر) |
| Growth | 2,490 ريال/شهر | شركات متوسطة (2000 lead/شهر) |
| Enterprise | مخصص | شركات كبرى |
| Agency Setup | 3,000-15,000 ريال | وكالات (لكل عميل) |
| Agency MRR | 20-30% من MRR | وكالات (متكرر) |
---
## Payment Methods (كلها جاهزة)
| الطريقة | الاستخدام | الحالة |
|---------|----------|--------|
| تحويل بنكي | أي مبلغ | جاهز |
| STC Pay | سريع | جاهز |
| فاتورة يدوية | رسمي | جاهز |
| Moyasar | تلقائي | ينتظر KYC |
---
## Red Flags (توقف عن الاستهداف لو)
- الشخص قال "إيقاف" أو "لا" صريحة → أوقف فوراً
- أرسلت 3 follow-ups بدون رد → أوقف
- القطاع ما فيه leads (مثال: حكومي صرف) → تجاوز
- الشخص طلب NDA قبل demo → red flag (يبي يسرق الفكرة)
## Green Flags (ضاعف الجهد لو)
- "كم السعر؟" → جاهز يشتري
- "عندنا 500+ lead/شهر" → high volume = high value
- "ممكن تجربة؟" → pilot ready
- "عندي وكالة" → partner ready
- "أعرف ناس ممكن يستفيدون" → referral goldmine

View File

@ -0,0 +1,77 @@
# Dealix — Demo Booking Runbook
## How to Book First Demo
1. Prospect responds positively to outreach message
2. Send Calendly link: `calendly.com/sami-assiri11/dealix-demo`
3. Or propose 2 specific times: "يناسبك بكرة 11 ص أو 3 م؟"
4. Calendly sends confirmation to both parties
## Demo Preparation (30 min before)
- [ ] Open prospect's website in a tab
- [ ] Run `POST /api/v1/prospect/enrich-tech` with their domain — note what tools they use
- [ ] Run `POST /api/v1/prospect/route` with their company/sector — note classification
- [ ] Prepare 1 sector-specific pain point to mention
- [ ] Have Calendly open for follow-up booking if needed
- [ ] Have pilot offer ready (499 SAR / 7 days)
## Demo Flow (20 minutes)
**0:00-2:00 — Context Check**
- "شكراً على وقتك. قبل ما نبدأ — وش أكبر تحدي عندكم في متابعة الـ leads حالياً؟"
- Listen. Take note.
**2:00-5:00 — Discovery (3 questions)**
1. "كم lead تقريباً تستقبلون شهرياً؟"
2. "كم منهم يُتابع خلال أول ساعة؟"
3. "إيش يصير لـ leads اللي ما يُرد عليهم بسرعة؟"
**5:00-12:00 — Show Dealix**
- Show pricing/plans API response (real, live)
- Show enrich-tech on their domain (real detection)
- Show prospect/route classification
- Show prospect/message with their company name → generated Arabic message
- Explain: "هذا يصير تلقائي لكل lead يجيكم"
**12:00-16:00 — ROI Discussion**
- "لو عندكم [X] lead/شهر، و30% تضيع، يعني [Y] فرصة ضائعة"
- "Dealix يرد خلال 45 ثانية — كم من هذول ممكن تنحفظ؟"
- Do not guarantee numbers. Say "نقيسها معكم خلال 7 أيام"
**16:00-18:00 — Handle Objections**
- Price: "نبدأ بـ pilot 7 أيام بـ 499 ريال — لو ما شفت قيمة، استرداد كامل"
- CRM: "Dealix يشتغل فوق CRM الحالي — ما يستبدله"
- Arabic: "نبدأ manual + AI-assisted — نراجع أول الردود معك"
- Privacy: "متوافق PDPL — بيانات كل عميل منفصلة"
**18:00-20:00 — Close**
- Strong: "نبدأ الـ pilot الأسبوع الجاي؟"
- Medium: "أرسل لك تفاصيل الـ pilot وتقرر؟"
- Soft: "فكّر فيها — أتابع معك بكرة"
## After Demo (same day)
- [ ] Update tracker: demo_done = yes
- [ ] Send follow-up message:
```
شكراً على وقتك اليوم [الاسم].
ملخص: نقدر نجرب Dealix على [X] leads عندكم لمدة 7 أيام.
الهدف: نقيس سرعة الرد وعدد المؤهلين والمواعيد المحجوزة.
التكلفة: 499 ريال — مع ضمان استرداد.
جاهز نبدأ؟
```
- [ ] If yes → send payment request (bank/STC Pay)
- [ ] If "need time" → schedule follow-up in 48h
- [ ] If no → ask "هل تعرف أحد ممكن يستفيد؟"
## What Qualifies a Successful Demo
- Prospect asks about pricing or pilot → high intent
- Prospect says "أبي أجرب" → convert immediately
- Prospect asks technical questions → engaged (answer, don't oversell)
- Prospect goes quiet → follow up in 24h, not immediately
## Recording
- Use Loom or OBS to record screen during demo (ask permission first)
- Save recording for internal review
- Do not share recording externally without consent

View File

@ -0,0 +1,133 @@
# Dealix — First 5 Outreach Messages
**Channel:** WhatsApp warm (people you know) + LinkedIn (professional contacts)
**Rules:** No spam. No cold WhatsApp blast. Personalized. Professional. Opt-out included.
---
## Message 1 — SaaS Founder (warm contact)
**Target:** صديق/معرفة يملك شركة SaaS سعودية
**Pain:** leads تجي من الموقع وتبرد بسبب تأخر الرد
```
السلام عليكم [الاسم]،
أطلقت Dealix — نظام يرد على leads شركتك بالعربي
خلال 45 ثانية، يؤهلهم، ويحجز مواعيد تلقائياً.
أبحث عن 3 أصدقاء لتجربة pilot 7 أيام بـ 499 ريال.
فكرت فيك لأن [الشركة] عندها leads من الموقع.
مهتم نتكلم 10 دقائق؟
```
**Follow-up 24h:**
```
سامي هنا — تابع على رسالة أمس عن Dealix.
باختصار: pilot 7 أيام على leads عندكم. يناسبك؟
```
**Follow-up 72h:**
```
آخر متابعة — لو مو الوقت المناسب أفهم تماماً.
لو بالمستقبل يناسب، أنا موجود. شكراً [الاسم].
```
---
## Message 2 — Agency Owner
**Target:** صاحب وكالة تسويق رقمي
**Pain:** عملاءه يجيبون leads بالإعلانات لكن المتابعة ضعيفة
```
السلام عليكم [الاسم]،
أشتغل على Dealix — طبقة AI بالعربي ترد على leads عملاء
الوكالات وتأهلهم وتحجز مواعيد.
الفكرة لكم: تبيعونها كـ setup + retainer. العميل يشوف
ROI أسرع — أنتم تزيدون MRR.
نبدأ بـ pilot مجاني لأول عميل عندكم.
20 دقيقة أشرح الآلية؟
calendly.com/sami-assiri11/dealix-demo
```
---
## Message 3 — Real Estate Company
**Target:** شركة عقارية أو مطوّر
**Pain:** استفسارات كثيرة عن الأسعار والمواقع تضيع
```
السلام عليكم [الاسم]،
لاحظت إن شركات العقار تستقبل عشرات الاستفسارات يومياً
عن الأسعار والمواقع — وكثير منها تضيع بسبب تأخر الرد.
Dealix يرد خلال 45 ثانية بالعربي، يسأل عن الميزانية
والموقع المفضل، ويحجز معاينة تلقائياً.
عندنا تجربة 7 أيام بـ 499 ريال. يناسبك أوريك مثال؟
إذا ما يناسبكم، قولوا "إيقاف" ولن أتواصل مرة ثانية.
```
---
## Message 4 — B2B Services (Logistics/Construction)
**Target:** شركة خدمات B2B (شحن، مقاولات)
**Pain:** طلبات عروض أسعار متكررة تحتاج فرز
```
السلام عليكم [الاسم]،
في قطاع [المقاولات/الشحن]، طلبات عروض الأسعار تجي
كثيرة — لكن فرز الجاد من غيره ياخذ وقت.
Dealix يستقبل الطلب بالعربي، يسأل عن نوع المشروع
والميزانية والجدول الزمني، ويصنّف الجدية قبل ما يوصل لفريقكم.
تجربة 7 أيام بـ 499 ريال. مهتم بمثال مبني على نشاطكم؟
لو ما يناسبكم: "إيقاف".
```
---
## Message 5 — Warm Referral Ask
**Target:** أي معرفة لها شبكة في الأعمال السعودية
**Pain:** N/A — you're asking for an intro
```
السلام عليكم [الاسم]،
أطلقت مشروع Dealix — نظام يساعد الشركات السعودية
ترد على استفسارات العملاء بسرعة وتأهلهم بالعربي.
سؤال سريع: تعرف أحد عنده شركة (عقار، وكالة تسويق،
خدمات B2B) وعنده leads كثيرة ومتابعة ضعيفة؟
لو تعرّفني عليه — 10% من أول سنة اشتراكه لك.
شكراً مقدماً!
```
---
## Lead Tracker (first 5)
| # | الاسم | الشركة | القطاع | المصدر | الرسالة | مرسلة؟ | الرد | Demo؟ | التالي |
|---|-------|--------|--------|--------|---------|--------|------|-------|--------|
| 1 | | | SaaS | warm | Msg 1 | | | | |
| 2 | | | Agency | warm | Msg 2 | | | | |
| 3 | | | Real Estate | cold/warm | Msg 3 | | | | |
| 4 | | | B2B Services | cold/warm | Msg 4 | | | | |
| 5 | | | Referral | warm | Msg 5 | | | | |
**Sami fills in names + sends → marks "مرسلة" → updates "الرد" when reply arrives.**

View File

@ -0,0 +1,168 @@
# Dealix — Full Ops Launch Runbook
**Version:** 1.0.0
**Date:** 2026-04-25
---
## Launch Status
| System | Status | Evidence |
|--------|--------|---------|
| Backend (Railway) | LIVE | healthz=200, pricing=200, route/message/enrich=200 |
| Landing (dealix.me) | LIVE | Returns 200 |
| Frontend (Next.js) | DEPLOYED | Marketers page rewritten as sales page |
| Manual Payment | READY | Bank transfer + STC Pay documented |
| Automated Payment | BLOCKED | Moyasar returns 502 |
| Outreach Templates | READY | 5 messages + 60 targets + automation endpoints |
| Customer Onboarding | READY | Pilot template + success playbook |
| Monitoring | PARTIAL | GitHub Actions healthcheck; Sentry not configured |
## What Is Live
- `/healthz` → 200
- `/api/v1/pricing/plans` → 3 plans in SAR
- `/api/v1/prospect/route` → rules-based lead classification
- `/api/v1/prospect/message` → Arabic message generation
- `/api/v1/prospect/enrich-tech` → real tech stack detection
- `/api/v1/automation/email/generate` → personalized email + follow-ups
- `/api/v1/automation/compliance/check` → compliance gate
- `/api/v1/automation/reply/classify` → 12-category reply handler
- `dealix.me` → landing page
- `trial-signup.html` → lead capture form
## What Is Not Live
- LLM personalization (needs GROQ_API_KEY)
- Web discovery (needs GOOGLE_SEARCH_API_KEY)
- PostHog funnel tracking (needs POSTHOG_API_KEY)
- Sentry error alerting (needs SENTRY_DSN)
- Moyasar automated checkout (returns 502)
- HubSpot CRM sync (needs HUBSPOT_API_KEY)
- Gmail automated send (needs OAuth setup)
## Manual Blockers (Sami-Only)
| Blocker | Action | Time | Priority |
|---------|--------|------|----------|
| GROQ_API_KEY | Add in Railway env Dealix/web | 3 min | P0 |
| GOOGLE_SEARCH_API_KEY + CX | Add in Railway | 5 min | P0 |
| SENTRY_DSN | Add in Railway | 3 min | P0 |
| POSTHOG_API_KEY + HOST | Add in Railway | 3 min | P0 |
| Send first 5 messages | WhatsApp + LinkedIn | 30 min | P0 |
| Book first demo | Calendly | Depends on replies | P0 |
| Test Moyasar key | curl from terminal | 5 min | P1 |
| Rollback drill | SSH to Hetzner Console | 15 min | P1 |
| DB restore drill | SSH to Hetzner Console | 15 min | P1 |
---
## Daily Checklist (until first 3 paid clients)
### Morning (08:00)
- [ ] Check Railway healthz → 200?
- [ ] Check Sentry for overnight errors (if configured)
- [ ] Review replies from yesterday
- [ ] Classify replies (use /api/v1/automation/reply/classify)
- [ ] Select today's 5-10 outreach targets
### Midday (12:00)
- [ ] Send 5 outreach messages (WhatsApp warm + LinkedIn)
- [ ] Follow up on Day +2 / +5 leads
- [ ] Respond to all positive replies within 2 hours
- [ ] Book demos for interested prospects
### Afternoon (16:00)
- [ ] Run demos (if scheduled)
- [ ] Send pilot offers to demo'd prospects
- [ ] Publish 1 LinkedIn/X post
### Evening (20:00)
- [ ] Update lead tracker
- [ ] Count: sent / replied / demo'd / offered / paid
- [ ] Plan tomorrow's targets
- [ ] Handle late replies
---
## Payment Test Checklist (for Sami)
### Manual Payment Test
1. Ask a friend to transfer 1 SAR via bank or STC Pay
2. Verify receipt in bank app
3. Mark test as passed
4. This is your production payment path until Moyasar works
### Moyasar Diagnostic
1. Open [dashboard.moyasar.com](https://dashboard.moyasar.com)
2. Check Account Status (Active? Pending? Suspended?)
3. Check API Keys → is `sk_live_` the current key?
4. Test from terminal:
```
curl -u "sk_live_YOUR_KEY:" https://api.moyasar.com/v1/invoices \
-d "amount=100" -d "currency=SAR"
```
5. If "authentication_error" → key mismatch, regenerate
6. If "account_inactive" → complete KYC
7. If success → Railway env has whitespace, re-paste carefully
---
## Outreach Checklist
- [ ] 5 warm WhatsApp messages sent (from FIRST_5_OUTREACH.md)
- [ ] 1 LinkedIn founder post published
- [ ] 1 LinkedIn DM to agency partner
- [ ] All follow-ups current (no lead older than 3 days without follow-up)
- [ ] Tracker updated with today's activity
## Demo Checklist
- [ ] Prospect's website reviewed
- [ ] enrich-tech run on their domain
- [ ] 20-minute demo completed per DEMO_BOOKING_RUNBOOK.md
- [ ] Follow-up sent same day
- [ ] Pilot offer sent if positive
## Rollback Checklist (if something breaks)
1. Railway → Deployments → click previous green deployment → Redeploy
2. If Railway is down: Hetzner Console → `systemctl restart dealix-api`
3. If DB corrupted: restore from Railway Postgres auto-backup
4. If code breaks: `git revert HEAD && git push origin main`
## DB Restore Checklist
1. Railway Postgres has automatic daily backups
2. Railway → Postgres service → Backups → Restore
3. After restore: verify `/api/v1/dashboard/metrics` returns data
## Incident Checklist
1. Check Railway logs → identify error
2. If 502/503: restart deployment
3. If code bug: revert last commit
4. If env issue: check Variables tab
5. If DNS: check GoDaddy/Cloudflare
6. Notify any active pilot clients if downtime > 30 minutes
---
## Do Not Touch List
- Do NOT add new LLM providers
- Do NOT rebuild dashboard
- Do NOT start v3.1
- Do NOT implement Temporal/LangGraph/Qdrant
- Do NOT build voice/webchat/mobile
- Do NOT expand to UAE/Egypt
- Do NOT hire before 3 paid clients
- Do NOT spend on ads before manual outreach proves conversion
---
## Definition of Launch-Ready
All of these must be true:
- [ ] Railway healthz=200
- [ ] 4 env keys added (GROQ, GOOGLE, SENTRY, POSTHOG)
- [ ] 5+ outreach messages sent
- [ ] 1+ demo booked
- [ ] Manual payment path tested (1 SAR friend test)
## Definition of Revenue-Live
All of the above, plus:
- [ ] 1+ pilot payment received (499 SAR)
- [ ] 1+ customer onboarded
- [ ] 1+ daily report sent to customer

View File

@ -0,0 +1,140 @@
# Dealix — Marketers Page: Service Sales Specification
**Purpose:** Convert `/marketers` from a link hub (current: 131 lines, 8 links) into a page that sells Dealix as a service marketers resell to their clients.
---
## Positioning
**Core message:** Dealix gives marketers a new revenue line — AI-powered lead response they sell as a service to their clients.
**Not:** "Here are resources for marketers"
**Instead:** "Here is a service you sell to your clients and earn recurring revenue"
**One-liner:** "أضف خدمة رد ذكي + تأهيل leads لعملائك — setup fee + MRR شهري متكرر"
---
## Target Segments
| Segment | Size | Pain | What They Buy |
|---------|------|------|---------------|
| **Performance agencies** (5-30 people) | ~200 in Saudi | Run ads, generate leads, but leads go cold — client blames the agency | A service they add on top of ads |
| **Full-service digital agencies** (10-50) | ~100 | Deliver websites + ads but no post-lead follow-up — churn risk | Retention tool for their clients |
| **Freelance marketers** | ~500+ | Want recurring income beyond project fees | Low-effort MRR add-on |
| **CRM/automation consultants** | ~50 | Implement HubSpot/Zoho but client doesn't use it well | Lead response layer they manage |
| **WhatsApp marketing specialists** | ~100 | Know WhatsApp works for Saudi but can't scale responses | AI response they configure |
---
## Page Sections (top to bottom)
### 1. Hero
- **Headline AR:** "حوّل وكالتك إلى ماكينة إيرادات متكررة"
- **Subheadline:** "أضف خدمة الرد الذكي على leads عملائك — setup fee لك + MRR شهري متكرر"
- **Primary CTA:** "احجز مكالمة شراكة" → calendly.com/sami-assiri11/dealix-demo
- **Secondary CTA:** "شوف الباقات" → scroll to packages
### 2. Problem Statement
"عملاءك يصرفون 10,000+ ريال/شهر على إعلانات ويجيبون leads.
لكن 70% من الـ leads تضيع لأن:
- ما أحد يرد خلال أول ساعة
- المتابعة يدوية وعشوائية
- فريق العميل مشغول
العميل يلوم الوكالة. الحقيقة: المشكلة مو في الإعلان — المشكلة في الرد."
### 3. Solution
"Dealix يعطيك الحل تبيعه لعملائك:
- AI يرد بالعربي خلال 45 ثانية على كل lead
- يسأل أسئلة التأهيل (ميزانية؟ جدية؟ موعد؟)
- يحجز اجتماع أو يحوّل للمبيعات
- يرسل تقرير يومي
أنت تبيع الخدمة. نحن نشغّل النظام. تحصّل setup fee + نسبة شهرية."
### 4. Packages Marketers Can Sell
| الباقة | Setup Fee (لك) | شهري (العميل يدفع) | نصيبك الشهري | ما يحصل العميل |
|--------|---------------|-------------------|-------------|---------------|
| **الأساسية** | 3,000 SAR | 990 SAR | 297 SAR (30%) | رد AI على واتساب + تأهيل + تقرير أسبوعي |
| **المتقدمة** | 7,000 SAR | 2,490 SAR | 622 SAR (25%) | + حجز مواعيد + CRM sync + follow-up |
| **المؤسسية** | 15,000 SAR | مخصص | 20-30% | + تقارير executive + تكامل كامل |
### 5. Workflows Marketers Can Run
**Workflow 1 — Performance Agency:**
1. تشغّل حملة إعلانية لعميل
2. Leads تنزل → Dealix يرد فوراً
3. Leads المؤهلة تتحول لمواعيد
4. العميل يشوف ROI أعلى → يجدد → أنت تحصّل MRR
**Workflow 2 — Freelance Marketer:**
1. تقدم عرض "إدارة حملات + رد ذكي"
2. تسعّر الخدمة كاملة (حملة + Dealix)
3. Dealix يشتغل بالخلفية
4. أنت تركز على الاستراتيجية والمحتوى
**Workflow 3 — CRM Consultant:**
1. تنفذ HubSpot/Zoho لعميل
2. تضيف Dealix كطبقة response
3. CRM يمتلئ بـ leads مؤهلة تلقائياً
4. العميل يشوف قيمة CRM أسرع → retention
### 6. Agency Use Cases
| القطاع | المشكلة | Dealix يحل |
|--------|---------|-----------|
| عقارات | 500+ استفسار/شهر، رد بعد ساعات | رد فوري + تأهيل الميزانية + حجز معاينة |
| مقاولات | طلبات عروض أسعار تحتاج فرز | فرز + تصنيف الجدية قبل تحويل للمهندسين |
| فنادق/قاعات | استفسارات حجز يومية | رد + تأهيل التاريخ والعدد + حجز مبدئي |
| عيادات | مرضى يسألون عن مواعيد وأسعار | رد تلقائي + حجز موعد |
| تجارة إلكترونية | رسائل واتساب عن المنتجات | رد + توجيه للشراء |
### 7. Freelancer Use Cases
| السيناريو | كيف تربح |
|-----------|---------|
| "أنا أسوي سوشيال ميديا لـ 5 عملاء" | أضف Dealix لكل عميل = 5 × 990 = 4,950 SAR/شهر إضافي |
| "أنا مستشار CRM" | أضف Dealix كخدمة after-implementation = retainer شهري |
| "أنا أدير إعلانات" | أضف "الرد الذكي" كـ add-on = يميّزك عن المنافسين |
### 8. FAQs
| السؤال | الجواب |
|--------|--------|
| كم ياخذ الإعداد لكل عميل؟ | يوم واحد للأساسية، 3 أيام للمتقدمة |
| هل أحتاج خبرة تقنية؟ | لا — نحن نسوي الإعداد والتشغيل |
| كم أقدر أربح؟ | عميل واحد = 3,000 setup + ~990/شهر = 14,880 SAR/سنة |
| هل فيه عقد طويل؟ | لا — شهري لك ولعميلك |
| ماذا لو العميل ما عجبه؟ | 30 يوم ضمان استرداد |
| هل يشتغل مع HubSpot/Zoho؟ | نعم — CRM sync متاح |
| هل يفهم العربي السعودي؟ | نعم — مبني Arabic-first |
| كم عميل أقدر أضيف؟ | بلا حدود — كل عميل بباقته |
| هل بياناتي وبيانات عملائي آمنة؟ | نعم — PDPL compliant + بيانات منفصلة لكل عميل |
| كيف أبدأ؟ | احجز مكالمة شراكة 20 دقيقة → نسوي pilot مجاني لأول عميل |
### 9. Proof/Trust Blocks
- "مبني للسوق السعودي — عربي أولاً"
- "متوافق مع نظام حماية البيانات الشخصية (PDPL)"
- "بيانات كل عميل منفصلة تماماً"
- "ضمان استرداد 30 يوم"
- "بدون عقد سنوي — شهري"
- Dealix logo + dealix.me
### 10. Final CTA
"ابدأ بأول عميل — مجاناً"
"نسوي pilot 7 أيام لأول عميل عندك بدون تكلفة. لو اشتغل → تبدأ تحصّل."
→ calendly.com/sami-assiri11/dealix-demo
---
## Conversion Goals
- Primary: Partner books Calendly call
- Secondary: Partner submits contact form
- Tertiary: Partner shares page with colleague
## Implementation
- File: `frontend/src/app/marketers/page.tsx`
- No new dependencies — uses existing Tailwind + lucide-react
- Arabic RTL primary, English secondary where needed

View File

@ -0,0 +1,81 @@
# Dealix — Execution Plan: 24h / 7d / 30d
**Starting state:** 28/39 gates closed. Product live. 0 revenue. 0 messages sent.
---
## Next 24 Hours
| # | Deliverable | Owner | Dependency | Definition of Done | Metric | Risk |
|---|-------------|-------|------------|-------------------|--------|------|
| 1 | Add GROQ_API_KEY in Railway env Dealix/web | Sami | console.groq.com (free) | `/api/v1/prospect/search-diag` shows GROQ set | LLM features activate | Key in wrong env (use Dealix, not Agent branch) |
| 2 | Add GOOGLE_SEARCH_API_KEY + CX in Railway | Sami | Google Cloud Console | `/api/v1/prospect/search-diag` shows Google set | Web discovery active | Needs billing account for Maps |
| 3 | Add SENTRY_DSN in Railway | Sami | sentry.io free plan | Sentry receives test error | Error alerting active | None |
| 4 | Send 5 WhatsApp warm messages | Sami | Personal contacts who own businesses | 5 conversations started | pipeline > 0 | Low reply rate from cold — use warm only |
| 5 | Publish Founder Launch Post on LinkedIn | Sami | LinkedIn account | Post visible + URL captured | Inbound starts | None |
| 6 | Send 1 LinkedIn DM to agency partner | Sami | LinkedIn | Connection request sent | Partner pipeline > 0 | May take days for acceptance |
**24h success = 3 keys added + 5 messages sent + 1 post published.**
---
## Next 7 Days
| # | Deliverable | Owner | Dependency | Definition of Done | Metric | Risk |
|---|-------------|-------|------------|-------------------|--------|------|
| 1 | 30+ outreach messages across channels | Sami | Templates in COMMAND_CENTER | Messages logged in tracker | 30 touches | Fatigue — batch 5/day |
| 2 | First demo booked via Calendly | Sami | At least 1 positive reply | Calendly notification received | 1 demo | Low conversion — try warm contacts first |
| 3 | First pilot offered (499 SAR) | Sami | Completed demo | Payment request sent | 1 offer | Prospect may need time |
| 4 | First partner contacted | Sami | AGENCY_PARTNER_OFFER.md | Agency responds | 1 partner conversation | Agency may want proof first |
| 5 | Moyasar diagnostic completed | Sami | Moyasar dashboard access | Know if live key works or needs fix | Unblocks automated payment | KYC may take days |
| 6 | PostHog receives 1+ event | Sami | POSTHOG_API_KEY added | Event visible in PostHog dashboard | Funnel measurement starts | Free plan sufficient |
| 7 | 3 LinkedIn posts published | Sami | Content from COMMAND_CENTER | Posts visible | Inbound pipeline | Consistency matters more than perfection |
**Week 1 success = 1 demo + 1 pilot offer + 3 posts + partner motion started.**
---
## Next 30 Days
| # | Deliverable | Owner | Dependency | Definition of Done | Metric | Risk |
|---|-------------|-------|------------|-------------------|--------|------|
| 1 | 200+ outreach touches total | Sami | Sustained daily activity | Tracker shows 200+ rows | Volume | Burnout — keep to 10/day |
| 2 | 5 demos completed | Sami | Positive replies | Calendly shows 5 past events | Demo rate | Need warm + cold mix |
| 3 | 3 pilot payments received | Sami | Completed demos | Bank/STC Pay proof | 1,497 SAR MRR | Conversion uncertainty |
| 4 | 1 agency partner active | Sami | Partner signs + brings client | Partner's client in pilot | Partner channel open | Agencies want proof |
| 5 | Marketers page rewritten | Claude | MARKETERS_PAGE_PLAN.md | Page sells services, not just links | Page conversion | TSX rewrite needed |
| 6 | Moyasar live checkout working | Sami | KYC + key fix | 1 SAR test transaction succeeds | Automated payment | May need new key |
| 7 | Case study draft from first client | Sami | First pilot completed | 1-page results document | Social proof | Client may not agree to name |
| 8 | Rollback drill completed | Claude/Sami | SSH access to server | Documented rollback in < 5 min | Recovery confidence | SSH currently blocked |
| 9 | 10 LinkedIn posts total | Sami | Weekly posting cadence | Posts visible | Brand building | Consistency > perfection |
| 10 | PostHog funnel visible | Sami/Claude | POSTHOG key + landing events | landing → signup → demo → paid visible | Data-driven decisions | Free plan limits |
**Month 1 success = 3 paid pilots (1,497 SAR) + 1 partner + funnel visible + case study draft.**
---
## Revenue Trajectory (conservative)
| Week | Touches | Replies | Demos | Pilots | Paid | MRR (SAR) |
|------|---------|---------|-------|--------|------|-----------|
| 1 | 30 | 3 | 1 | 0 | 0 | 0 |
| 2 | 60 | 8 | 2 | 1 | 0 | 0 |
| 3 | 90 | 12 | 3 | 2 | 1 | 499 |
| 4 | 120 | 15 | 5 | 3 | 3 | 1,497 |
| **Total** | **300** | **38** | **11** | **6** | **3** | **1,497** |
Assumes: 13% reply rate, 29% demo-from-reply rate, 55% pilot-from-demo, 50% paid-from-pilot.
---
## What NOT to Do in 30 Days
- Do NOT rebuild the dashboard
- Do NOT add new LLM providers
- Do NOT implement Temporal/LangGraph/Qdrant
- Do NOT build voice receptionist or webchat
- Do NOT start v3.1
- Do NOT expand to UAE/Egypt
- Do NOT build mobile app
- Do NOT hire before first 3 paid clients
- Do NOT spend on ads before proving manual outreach converts

View File

@ -0,0 +1,55 @@
# Dealix — Railway Environment Keys
**Where to add:** Railway → Project → Service "web" → Variables tab → Review → Deploy
---
## P0 — Required for Launch
| Variable | Source | Cost | Effect When Missing | Verification |
|----------|--------|------|-------------------|-------------|
| `GROQ_API_KEY` | [console.groq.com/keys](https://console.groq.com/keys) | Free tier available | LLM features degraded (rules-only) | `curl /api/v1/prospect/search-diag` shows `GROQ_API_KEY.set: true` |
| `GOOGLE_SEARCH_API_KEY` | [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials) | 100 queries/day free | `/prospect/search` returns 503 | search-diag shows key set |
| `GOOGLE_SEARCH_CX` | Fixed value: `75ae2277dfd754a1a` | Free | Same as above | search-diag shows CX set |
| `SENTRY_DSN` | [sentry.io](https://sentry.io) → Create Python project → copy DSN | Free plan | No error alerting | Sentry receives test error |
| `POSTHOG_API_KEY` | [posthog.com](https://posthog.com) → Project Settings → API Key | Free plan | No funnel tracking | PostHog dashboard shows events |
| `POSTHOG_HOST` | `https://us.i.posthog.com` or `https://eu.i.posthog.com` | — | PostHog events go nowhere | Same as above |
## P1 — Required for Automated Payment
| Variable | Source | Cost | Effect When Missing |
|----------|--------|------|-------------------|
| `MOYASAR_SECRET_KEY` | [dashboard.moyasar.com](https://dashboard.moyasar.com) → API Keys | Per-transaction fee | Checkout returns 502 |
| `MOYASAR_PUBLISHABLE_KEY` | Same dashboard | — | Frontend checkout broken |
| `MOYASAR_WEBHOOK_SECRET` | Moyasar → Webhooks → Secret | — | Webhook signature validation fails |
**Note:** Moyasar keys appear to be set but checkout returns 502. See REVENUE_READINESS_CHECKLIST.md → Moyasar Diagnostic Checklist.
## P2 — Optional Enrichment (after first customer)
| Variable | Source | Effect When Missing |
|----------|--------|-------------------|
| `GOOGLE_MAPS_API_KEY` | Google Cloud → Maps → Places API | Local discovery disabled |
| `SENDGRID_API_KEY` | sendgrid.com | No automated email |
| `WHATSAPP_ACCESS_TOKEN` | Meta Business | No WhatsApp API |
| `HUBSPOT_API_KEY` | HubSpot → Private Apps | No CRM sync |
| `TAVILY_API_KEY` | tavily.com | No web crawl enrichment |
## Already Set (verified via search-diag)
| Variable | Status |
|----------|--------|
| `MOYASAR_SECRET_KEY` | Set (48 chars, sk_liv...) |
| `MOYASAR_WEBHOOK_SECRET` | Set (64 chars) |
| `MOYASAR_PUBLIC_KEY` | Set |
| `APP_URL` | Set |
| `DATABASE_URL` | Set (via Railway Postgres) |
---
## Critical Reminders
1. **Environment dropdown:** Make sure you're in environment "Dealix" (not "adventurous-tenderness" or other Agent branches)
2. **Service:** Variables go in service "web" (not Postgres or Redis)
3. **Deploy:** After adding variables, you MUST click Review → Deploy. Variables are staged until deployed.
4. **No secrets in chat:** Never paste API keys in any chat window. Add them directly in Railway.

View File

@ -0,0 +1,133 @@
# Dealix — Revenue Readiness Checklist
**Last verified:** 2026-04-25
**Railway healthz:** 200 (verified)
**Pricing API:** 200 (verified: Starter 999 / Growth 2999 / Scale 7999 SAR)
---
## 1. Pricing Path
| Item | Status | Evidence |
|------|--------|---------|
| Plans in API | DONE | `/api/v1/pricing/plans` returns 3 plans, SAR currency |
| Pilot offer defined | DONE | 499 SAR / 7 days / documented in COMMAND_CENTER.md |
| Agency pricing defined | DONE | AGENCY_PARTNER_OFFER.md: setup 3K-15K + 20-30% MRR |
| Public pricing page | NOT DONE | API exists but no public-facing pricing page on dealix.me |
**Blocker:** None for manual sales. Pricing page is P1 for inbound.
## 2. Invoice Path
| Item | Status | Evidence |
|------|--------|---------|
| Manual invoice template | DONE | FIRST_REVENUE_ATTEMPT in revenue-activation/ |
| Bank transfer details | DONE | Documented in COMMAND_CENTER.md |
| STC Pay | DONE | Ready |
| Moyasar live invoice | BLOCKED | `/api/v1/checkout` returns 502 — Moyasar-side issue |
| Moyasar sandbox | NOT TESTED | No `MOYASAR_TEST_SECRET_KEY` in Railway env |
**Workaround:** Manual invoice via bank transfer or STC Pay. Works today.
## 3. Payment Path
| Item | Status | Evidence |
|------|--------|---------|
| Bank transfer acceptance | READY | Account info available |
| STC Pay acceptance | READY | Number available |
| Manual proof workflow | READY | Customer sends screenshot → Sami confirms → onboarding starts |
| Moyasar automated payment | BLOCKED | 502 on checkout — Moyasar dashboard issue (KYC or key) |
| Moyasar test payment (1 SAR) | NOT TESTED | Needs `MOYASAR_TEST_SECRET_KEY` or Sami to test `sk_live_` via curl |
| Payment → onboarding trigger | MANUAL | On payment proof → start onboarding checklist within 4h |
**Manual payment path: FULLY OPERATIONAL.**
## 4. Booking Path
| Item | Status | Evidence |
|------|--------|---------|
| Calendly link | ACTIVE | calendly.com/sami-assiri11/dealix-demo |
| Landing → Calendly | DONE | trial-signup.html redirects on form submit |
| Email templates → Calendly | DONE | All email templates include Calendly link |
| Outreach messages → Calendly | DONE | All DM templates include Calendly link |
| Calendly → webhook | NOT TESTED | Code exists but no E2E booking verified |
| Calendly → CRM | NOT TESTED | HubSpot key missing in Railway |
**Booking path: OPERATIONAL for manual flow (link → booking → Sami sees in Calendly).**
## 5. CRM Sync Path
| Item | Status | Evidence |
|------|--------|---------|
| HubSpot connector code | EXISTS | `services/crm_sync_service.py` |
| HubSpot API key in Railway | MISSING | Not set in Railway env Dealix/web |
| E2E: lead → HubSpot contact | NOT TESTED | Cannot test without key |
| Manual CRM (Google Sheet) | READY | Tracker template in ops docs |
| Pipeline stages defined | DONE | new → sent → replied → demo → pilot → paid → churned |
**Manual CRM: READY. HubSpot automated: BLOCKED on API key.**
## 6. Follow-up Path
| Item | Status | Evidence |
|------|--------|---------|
| Reply classifier | DONE | `POST /api/v1/automation/reply/classify` — 12 categories |
| Follow-up templates (Day +2/+5/+10) | DONE | Generated per lead in email/generate endpoint |
| Arabic response templates | DONE | Pre-written Khaliji response per category |
| Gmail OAuth adapter | NOT BUILT | Needs Google Cloud OAuth setup |
| Manual follow-up | READY | Copy-paste from templates |
**Manual follow-up: READY. Gmail auto-send: BLOCKED on OAuth setup.**
## 7. Test Payment Path
| Test | Status | How to Test |
|------|--------|-------------|
| Moyasar sandbox | NOT TESTED | Add `MOYASAR_TEST_SECRET_KEY` (sk_test_...) in Railway → hit `/api/v1/checkout` |
| Moyasar live | BLOCKED | sk_live_ exists in Railway but returns 502 |
| Manual bank transfer | READY | Ask friend to transfer 1 SAR → verify receipt |
| Manual STC Pay | READY | Ask friend to STC Pay 1 SAR → verify receipt |
**Recommended first test:** Manual bank transfer from a friend. Zero technical dependency.
## 8. Manual Fallback Summary
| Function | Automated | Manual Fallback | Status |
|----------|-----------|----------------|--------|
| Lead response | AI endpoint exists | Sami responds manually | READY |
| Qualification | Automation endpoint | Sami asks questions manually | READY |
| Booking | Calendly auto | Sami proposes 2 times | READY |
| Payment | Moyasar | Bank/STC Pay + proof | READY |
| CRM | HubSpot | Google Sheet | READY |
| Follow-up | Gmail OAuth | Copy-paste templates | READY |
| Reporting | Dashboard | Manual WhatsApp/email to client | READY |
**Every function has a working manual fallback. No function is truly blocked.**
---
## Definition of Done for "Revenue Live"
| Gate | Status | What Proves It |
|------|--------|---------------|
| First 5 outreach messages sent | NOT DONE | Sami confirms SENT |
| First demo booked via Calendly | NOT DONE | Calendly notification |
| First pilot payment received (499 SAR) | NOT DONE | Bank/STC Pay screenshot |
| First customer onboarded (Day 0 checklist) | NOT DONE | Checklist filled |
| First daily report sent to customer | NOT DONE | WhatsApp/email sent |
| PostHog receives at least 1 event | NOT DONE | PostHog dashboard check |
| Manual payment log has 1 entry | NOT DONE | File updated |
**0/7 done. Revenue is NOT live. Product is live. The gap is sales activity, not engineering.**
---
## Moyasar Diagnostic Checklist (for Sami)
If 502 persists, check these in order:
1. Moyasar dashboard → Account Status: Active? Pending KYC? Suspended?
2. Moyasar dashboard → API Keys: Is `sk_live_` the latest generated key?
3. Test from terminal: `curl -u "sk_live_...:" https://api.moyasar.com/v1/invoices -d "amount=100" -d "currency=SAR"`
4. If "authentication_error" → key wrong or regenerated. Get new key.
5. If "account_inactive" → KYC not complete. Finish KYC in Moyasar.
6. If success → Railway env has whitespace or wrong value. Re-paste key carefully.

View File

@ -0,0 +1,81 @@
# Dealix — 60 Saudi B2B Targets (Week 1)
## Direct Customers (20)
| # | Company | Sector | Website | Why Target | Message Angle |
|---|---------|--------|---------|-----------|---------------|
| 1 | Foodics | SaaS/F&B | foodics.com | HubSpot+WhatsApp detected | "شفت إنكم تستخدمون HubSpot — كم lead يضيع بسبب بطء الرد؟" |
| 2 | Lucidya | SaaS/Social | lucidya.com | 4 tools detected | "شفت حجم الأدوات عندكم — Dealix يكمّلها بالرد الفوري" |
| 3 | Lean Technologies | SaaS/Fintech | leantech.me | Saudi fintech scaling | "فريق مبيعات صغير + leads كثيرة = فرصة" |
| 4 | Tamara | BNPL | tamara.co | 3 tools detected | "عندكم merchants كثيرة تحتاج متابعة" |
| 5 | Rewaa | POS/Retail | rewaa.com | WhatsApp+GTM | "تجار التجزئة يسألون كثير — Dealix يرد بدالكم" |
| 6 | Qoyod | Accounting | qoyod.com | HubSpot+WhatsApp | "عملاء المحاسبة يسألون نفس الأسئلة — AI يرد" |
| 7 | Maqsam | Cloud Phone | maqsam.com | HubSpot+Framer | "أنتم تبيعون communications — جربوا AI sales" |
| 8 | Hakbah | Savings | hakbah.com | WooCommerce+GTM+GA4 | "لاحظت forms كثيرة — كم يُتابع فعلاً؟" |
| 9 | Sary | B2B Supply | sary.com | 3 tools | "توزيع B2B = leads كثيرة = Dealix يأهّل" |
| 10 | BRKZ | Construction | brkz.com | 2 tools | "مقاولين يسألون عن الأسعار — AI يرد ويأهّل" |
| 11 | Aqar | Real Estate | aqar.fm | High volume leads | "عقار = 500+ lead/شهر — كم يُتابع؟" |
| 12 | Jarir Online | Retail | jarir.com | ecommerce B2B | "استفسارات الشركات على الموقع — AI يحوّلها لصفقات" |
| 13 | Floward | Flowers/Corp | floward.com | Corporate orders | "طلبات B2B (تموين، هدايا) تحتاج متابعة سريعة" |
| 14 | Nana | Grocery | nana.sa | Delivery+B2B | "طلبات المطاعم والشركات = pipeline ثابت" |
| 15 | Morni | Roadside | morni.com | Service leads | "طلبات الشركات للعقود السنوية" |
| 16 | Lendo | Lending | lendo.sa | Fintech B2B | "طلبات التمويل تحتاج تأهيل سريع" |
| 17 | Unifonic | Communications | unifonic.com | 6 tools! Chili Piper+HubSpot+Zendesk | "أنتم في مجال التواصل — جربوا AI SDR" |
| 18 | HyperPay | Payments | hyperpay.com | Payment gateway | "merchants تسأل عن التفعيل — AI يرد" |
| 19 | Moyasar | Payments | moyasar.com | Payment gateway | "developers يسألون عن الـ API — AI يوجّه" |
| 20 | Tap Payments | Payments | tap.company | Mixpanel detected | "عملاء جدد يسألون — AI يأهّل ويحوّل" |
## Agency Partners (20)
| # | Company | Type | Why Target | Offer |
|---|---------|------|-----------|-------|
| 1 | Peak Content | Content Marketing | Saudi top agency | Setup 5K + 25% MRR |
| 2 | Digital8 | Digital Agency | Riyadh | Setup 3K + 20% MRR |
| 3 | Brand Lounge | Branding+Digital | Dubai/Riyadh | Setup 5K + 25% MRR |
| 4 | Wavy | Social Media | Saudi focused | Setup 3K + 20% MRR |
| 5 | Bold | Performance | Ads focused | Setup 3K + 20% MRR |
| 6 | Netaq Digital | Full service | Jeddah | Setup 3K + 20% MRR |
| 7 | Jarwal | Marketing | Riyadh | Setup 3K + 20% MRR |
| 8 | Zoomly | Growth Agency | Saudi | Setup 3K + 20% MRR |
| 9 | UTURN | Content | Saudi media | Content collab |
| 10 | Telfaz11 | Video Content | Saudi media | Content collab |
| 11 | Techieons | Tech Consulting | IT services | Implementation partner |
| 12 | Lean Business | Consulting | Strategy | Referral 10% |
| 13 | 2P Agency | Creative | Saudi creative | Setup 3K + 20% MRR |
| 14 | Shawaa | Performance | PPC/Social | Setup 3K + 20% MRR |
| 15 | Crowd | PR + Digital | Communications | Setup 5K + 25% MRR |
| 16 | Rab3i | Marketing | Saudi digital | Setup 3K + 20% MRR |
| 17 | Pioneers | Digital Marketing | MENA | Setup 5K + 25% MRR |
| 18 | Hayan | HubSpot Partner | CRM implementation | Implementation partner |
| 19 | Digiata | Automation | Marketing automation | Implementation partner |
| 20 | SocialEyes | Social Media | Riyadh/Dubai | Setup 3K + 20% MRR |
## Strategic Partners (10)
| # | Company | Type | Partnership Model |
|---|---------|------|------------------|
| 1 | Salla | E-commerce Platform | Integration + co-sell to Salla merchants |
| 2 | Zid | E-commerce Platform | Integration + co-sell to Zid merchants |
| 3 | Wafeq | Accounting SaaS | Cross-sell to Wafeq customers |
| 4 | Unifonic | Communications | Unifonic customers → Dealix |
| 5 | Tap Payments | Payment Gateway | Tap merchants → Dealix |
| 6 | Misk Foundation | Startup Community | Misk ecosystem companies |
| 7 | KAUST | Innovation Hub | KAUST startups |
| 8 | STC Solutions | Enterprise IT | Enterprise referral |
| 9 | Wamda Capital | VC | Portfolio companies |
| 10 | Sanabil Investments | PIF VC | Portfolio companies |
## Warm Network (10)
| # | Relationship | Why | Ask |
|---|-------------|-----|-----|
| 1 | صديق يملك شركة SaaS | يفهم المنتج | Pilot مباشر |
| 2 | صديق في وكالة تسويق | يبيع لعملائه | Partner |
| 3 | زميل سابق مدير مبيعات | يعرف المشكلة | Referral |
| 4 | قريب يملك متجر إلكتروني | واتساب كقناة | Pilot |
| 5 | معرفة من مؤتمر تقني | شبكة واسعة | Intro |
| 6 | صديق في عقارات | leads كثيرة | Pilot |
| 7 | زميل دراسة في fintech | يعرف founders | Intro |
| 8 | معرفة في F&B | سلسلة مطاعم | B2B pilot |
| 9 | مستشار أعمال | يعرف SMEs | Referral |
| 10 | Mentor من accelerator | شبكة founders | Intro |

View File

@ -1,131 +1,426 @@
import Link from "next/link";
import {
MessageCircle,
Download,
FileText,
CheckSquare,
Presentation,
Users,
TrendingUp,
Zap,
Shield,
Clock,
CheckCircle,
ArrowLeft,
ExternalLink,
Compass,
Calendar,
DollarSign,
Briefcase,
HelpCircle,
} from "lucide-react";
export const metadata = {
title: "Dealix — بوابة المسوّقين",
description: "دخول مباشر، تحميلات، قوالب واتساب، وروابط العروض القطاعية.",
title: "Dealix — برنامج شراكة الوكالات والمسوّقين",
description:
"حوّل وكالتك إلى ماكينة إيرادات متكررة. أضف خدمة الرد الذكي + تأهيل leads لعملائك — setup fee + MRR شهري.",
};
const links = [
const packages = [
{
title: "الخطة الاستراتيجية والمنافسة",
href: "/strategy",
desc: "لماذا Dealix، مراحل التنفيذ، وتحميل الوثيقة الكاملة.",
icon: Compass,
name: "الأساسية",
setupFee: "3,000",
monthly: "990",
partnerShare: "30%",
features: [
"رد AI بالعربي على واتساب أو فورم",
"5 أسئلة تأهيل مخصصة",
"تصنيف leads: حار / دافئ / بارد",
"تقرير أسبوعي",
],
cta: "ابدأ بأول عميل",
highlight: false,
},
{
title: "مركز الموارد (كل الروابط)",
href: "/resources",
desc: "ZIP، العروض، والملفات التسويقية.",
icon: Download,
name: "المتقدمة",
setupFee: "7,000",
monthly: "2,490",
partnerShare: "25%",
features: [
"كل مميزات الأساسية",
"حجز مواعيد تلقائي (Calendly)",
"ربط CRM ثنائي (HubSpot)",
"متابعة تلقائية Day +2 / +5 / +10",
"تقرير يومي",
],
cta: "الأنسب للوكالات",
highlight: true,
},
{
title: "فهرس الملفات الثابتة",
href: "/dealix-marketing/index.html",
desc: "نسخة HTML كاملة من بوابة الأصول.",
icon: FileText,
},
{
title: "قوالب واتساب (نسخ ولصق)",
href: "/dealix-marketing/marketers/whatsapp-playbook-ar.txt",
desc: "رسائل جاهزة — عدّل الاسم والرابط فقط.",
icon: MessageCircle,
},
{
title: "قائمة تحقق الدخول",
href: "/dealix-marketing/marketers/entry-checklist-ar.txt",
desc: "تأكد أنك غطيت الخطوات قبل التواصل مع العملاء.",
icon: CheckSquare,
},
{
title: "العروض القطاعية (10 قطاعات)",
href: "/dealix-presentations/00-dealix-company-master-ar.html",
desc: "ابدأ من ملف الشركة ثم اختر رقم القطاع.",
icon: Presentation,
external: false,
},
{
title: "هيكل العمولة (Markdown)",
href: "/dealix-marketing/Dealix_Marketing_Arsenal.md",
desc: "Silver / Gold / Platinum — راجع العقد الرسمي للأرقام النهائية.",
icon: FileText,
name: "المؤسسية",
setupFee: "15,000+",
monthly: "مخصص",
partnerShare: "20-30%",
features: [
"كل مميزات المتقدمة",
"قنوات متعددة (واتساب + إيميل + صوت)",
"لوحة تحكم تنفيذية",
"سير عمل موافقات",
"مدير حساب مخصص",
],
cta: "تواصل معنا",
highlight: false,
},
];
const workflows = [
{
icon: TrendingUp,
title: "وكالة أداء (Performance)",
desc: "تشغّل حملة → Leads تنزل → Dealix يرد فوراً → العميل يشوف ROI أعلى → يجدد → أنت تحصّل MRR",
},
{
icon: Briefcase,
title: "مسوّق مستقل (Freelancer)",
desc: 'تقدم عرض "إدارة حملات + رد ذكي" → Dealix يشتغل بالخلفية → أنت تركز على الاستراتيجية → دخل شهري متكرر',
},
{
icon: Users,
title: "مستشار CRM / RevOps",
desc: "تنفذ HubSpot لعميل → تضيف Dealix كطبقة response → CRM يمتلئ بـ leads مؤهلة → العميل يشوف قيمة CRM أسرع",
},
];
const faqs = [
{
q: "كم ياخذ الإعداد لكل عميل؟",
a: "يوم واحد للأساسية، 3 أيام للمتقدمة.",
},
{
q: "هل أحتاج خبرة تقنية؟",
a: "لا — نحن نسوي الإعداد والتشغيل بالكامل.",
},
{
q: "كم أقدر أربح من عميل واحد؟",
a: "عميل واحد = 3,000 setup + ~990/شهر = 14,880 ريال/سنة.",
},
{
q: "هل فيه عقد طويل؟",
a: "لا — شهري لك ولعميلك. بدون التزام سنوي.",
},
{
q: "ماذا لو العميل ما عجبه؟",
a: "30 يوم ضمان استرداد كامل.",
},
{
q: "هل يشتغل مع HubSpot / Zoho؟",
a: "نعم — CRM sync متاح في الباقة المتقدمة.",
},
{
q: "هل يفهم العربي السعودي؟",
a: "نعم — مبني Arabic-first بلهجة خليجية.",
},
{
q: "كيف أبدأ؟",
a: "احجز مكالمة شراكة 20 دقيقة → نسوي pilot مجاني لأول عميل عندك.",
},
];
const CALENDLY = "https://calendly.com/sami-assiri11/dealix-demo";
export default function MarketersPage() {
return (
<div className="min-h-screen bg-gradient-to-b from-slate-950 via-slate-900 to-teal-950 text-slate-100">
<div className="mx-auto max-w-2xl px-6 py-14">
<p className="text-sm font-semibold text-teal-400">Dealix Partner GTM</p>
<h1 className="mt-2 text-3xl font-bold tracking-tight">بوابة المسوّقين</h1>
<p className="mt-3 text-slate-400 leading-relaxed">
مسار واحد للدخول: افتح الروابط أدناه من نفس الموقع (لا حاجة لخادم 8000). انسخ قوالب
الواتساب من الملف النصي وعدّل{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 text-teal-200">
{`{الاسم}`}
</code>{" "}
و
<code className="rounded bg-white/10 px-1.5 py-0.5 text-teal-200">رابط موقعك</code>.
<div className="min-h-screen bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950 text-slate-100">
{/* ── Hero ── */}
<section className="mx-auto max-w-4xl px-6 pb-16 pt-20 text-center">
<span className="inline-block rounded-full bg-amber-500/10 px-4 py-1.5 text-sm font-medium text-amber-400 ring-1 ring-amber-500/20">
برنامج شراكة الوكالات والمسوّقين
</span>
<h1 className="mt-6 text-4xl font-extrabold leading-tight tracking-tight lg:text-5xl">
حوّل وكالتك إلى{" "}
<span className="bg-gradient-to-l from-amber-400 to-amber-600 bg-clip-text text-transparent">
ماكينة إيرادات متكررة
</span>
</h1>
<p className="mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-slate-300">
أضف خدمة الرد الذكي + تأهيل leads لعملائك. أنت تحصّل setup fee كامل + نسبة
شهرية متكررة. نحن نشغّل النظام.
</p>
<div className="mt-10 space-y-3">
{links.map((item) => (
<a
key={item.href}
href={item.href}
className="flex gap-4 rounded-2xl border border-white/10 bg-white/5 p-5 transition hover:border-teal-500/40 hover:bg-white/10"
>
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-teal-500/20 text-teal-300">
<item.icon className="h-6 w-6" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 font-semibold text-white">
{item.title}
<ExternalLink className="h-3.5 w-3.5 shrink-0 opacity-50" />
</div>
<p className="mt-1 text-sm text-slate-400">{item.desc}</p>
<p className="mt-2 truncate font-mono text-xs text-teal-500/80">{item.href}</p>
</div>
</a>
))}
<div className="mt-8 flex flex-wrap justify-center gap-4">
<a
href={CALENDLY}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-xl bg-amber-500 px-8 py-3.5 text-base font-bold text-slate-900 transition hover:bg-amber-400"
>
<Calendar className="h-5 w-5" />
احجز مكالمة شراكة
</a>
<a
href="#packages"
className="inline-flex items-center gap-2 rounded-xl border border-white/20 px-8 py-3.5 text-base font-medium transition hover:bg-white/10"
>
شوف الباقات
</a>
</div>
</section>
<div className="mt-12 rounded-2xl border border-teal-500/30 bg-teal-950/40 p-6">
<h2 className="flex items-center gap-2 text-lg font-semibold text-teal-200">
<MessageCircle className="h-5 w-5" />
تلميح واتساب سريع
</h2>
<p className="mt-2 text-sm leading-relaxed text-slate-300">
احفظ رسالة واحدة كقالب في واتساب (الأجهزة المدعومة) أو استخدم ملاحظات سريعة. لا ترسل
لعملاء نهائيين دون تنسيق مع فريق Dealix وحسب سياسة الاستخدام.
{/* ── Problem ── */}
<section className="border-y border-white/10 bg-slate-900/50 py-16">
<div className="mx-auto max-w-3xl px-6 text-center">
<h2 className="text-2xl font-bold">المشكلة اللي تعرفها</h2>
<p className="mt-4 text-lg leading-relaxed text-slate-300">
عملاءك يصرفون <strong className="text-white">10,000+ ريال/شهر</strong> على
إعلانات ويجيبون leads. لكن{" "}
<strong className="text-amber-400">70% تضيع</strong> لأن:
</p>
<div className="mt-6 grid gap-4 text-start sm:grid-cols-3">
{[
{ icon: Clock, text: "ما أحد يرد خلال أول ساعة" },
{ icon: MessageCircle, text: "المتابعة يدوية وعشوائية" },
{ icon: Users, text: "فريق العميل مشغول" },
].map((item) => (
<div
key={item.text}
className="flex items-start gap-3 rounded-xl border border-white/10 bg-white/5 p-4"
>
<item.icon className="mt-0.5 h-5 w-5 shrink-0 text-red-400" />
<span className="text-sm text-slate-300">{item.text}</span>
</div>
))}
</div>
<p className="mt-6 text-base text-slate-400">
العميل يلوم الوكالة. الحقيقة: المشكلة مو في الإعلان المشكلة في الرد.
</p>
</div>
</section>
<div className="mt-10 flex flex-wrap gap-4">
<Link
href="/"
className="inline-flex items-center gap-2 text-sm text-teal-400 hover:text-teal-300"
>
<ArrowLeft className="h-4 w-4 rotate-180" />
الصفحة الرئيسية
{/* ── Solution ── */}
<section className="py-16">
<div className="mx-auto max-w-3xl px-6 text-center">
<h2 className="text-2xl font-bold">
Dealix يعطيك الحل{" "}
<span className="text-amber-400">تبيعه لعملائك</span>
</h2>
<div className="mt-8 grid gap-6 text-start sm:grid-cols-2">
{[
"AI يرد بالعربي خلال 45 ثانية على كل lead",
"يسأل أسئلة التأهيل (ميزانية؟ جدية؟ موعد؟)",
"يحجز اجتماع أو يحوّل للمبيعات",
"يرسل تقرير يومي عن كل lead",
].map((text) => (
<div key={text} className="flex items-start gap-3">
<CheckCircle className="mt-0.5 h-5 w-5 shrink-0 text-emerald-400" />
<span className="text-slate-300">{text}</span>
</div>
))}
</div>
<p className="mt-8 rounded-xl border border-amber-500/20 bg-amber-500/5 p-4 text-base text-amber-200">
أنت تبيع الخدمة. نحن نشغّل النظام. تحصّل setup fee + نسبة شهرية.
</p>
</div>
</section>
{/* ── Workflows ── */}
<section className="border-y border-white/10 bg-slate-900/50 py-16">
<div className="mx-auto max-w-4xl px-6">
<h2 className="text-center text-2xl font-bold">كيف تشتغل حسب نوعك</h2>
<div className="mt-10 grid gap-6 sm:grid-cols-3">
{workflows.map((w) => (
<div
key={w.title}
className="rounded-2xl border border-white/10 bg-white/5 p-6"
>
<w.icon className="h-8 w-8 text-amber-400" />
<h3 className="mt-4 text-lg font-semibold">{w.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-slate-400">{w.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* ── Packages ── */}
<section id="packages" className="py-16">
<div className="mx-auto max-w-5xl px-6">
<h2 className="text-center text-2xl font-bold">
باقات تبيعها لعملائك
</h2>
<p className="mt-2 text-center text-slate-400">
Setup fee كامل لك + نسبة شهرية متكررة
</p>
<div className="mt-10 grid gap-6 lg:grid-cols-3">
{packages.map((pkg) => (
<div
key={pkg.name}
className={`relative flex flex-col rounded-2xl border p-6 ${
pkg.highlight
? "border-amber-500/50 bg-amber-500/5 ring-1 ring-amber-500/20"
: "border-white/10 bg-white/5"
}`}
>
{pkg.highlight && (
<span className="absolute -top-3 start-4 rounded-full bg-amber-500 px-3 py-0.5 text-xs font-bold text-slate-900">
الأكثر طلباً
</span>
)}
<h3 className="text-xl font-bold">{pkg.name}</h3>
<div className="mt-3">
<span className="text-sm text-slate-400">Setup fee لك:</span>
<span className="ms-2 text-2xl font-extrabold text-amber-400">
{pkg.setupFee}
</span>
<span className="text-sm text-slate-400"> ريال</span>
</div>
<div className="mt-1">
<span className="text-sm text-slate-400">العميل يدفع:</span>
<span className="ms-2 font-bold text-white">{pkg.monthly}</span>
<span className="text-sm text-slate-400"> ريال/شهر</span>
</div>
<div className="mt-1">
<span className="text-sm text-slate-400">نصيبك الشهري:</span>
<span className="ms-2 font-bold text-emerald-400">
{pkg.partnerShare}
</span>
</div>
<ul className="mt-4 flex-1 space-y-2">
{pkg.features.map((f) => (
<li key={f} className="flex items-start gap-2 text-sm text-slate-300">
<CheckCircle className="mt-0.5 h-4 w-4 shrink-0 text-emerald-500" />
{f}
</li>
))}
</ul>
<a
href={CALENDLY}
target="_blank"
rel="noopener noreferrer"
className={`mt-6 block rounded-xl py-3 text-center font-bold transition ${
pkg.highlight
? "bg-amber-500 text-slate-900 hover:bg-amber-400"
: "border border-white/20 text-white hover:bg-white/10"
}`}
>
{pkg.cta}
</a>
</div>
))}
</div>
</div>
</section>
{/* ── Revenue Calculator ── */}
<section className="border-y border-white/10 bg-slate-900/50 py-16">
<div className="mx-auto max-w-3xl px-6 text-center">
<DollarSign className="mx-auto h-10 w-10 text-amber-400" />
<h2 className="mt-4 text-2xl font-bold">كم تقدر تربح؟</h2>
<div className="mt-6 grid gap-4 sm:grid-cols-3">
<div className="rounded-xl border border-white/10 bg-white/5 p-4">
<div className="text-3xl font-extrabold text-amber-400">14,880</div>
<div className="mt-1 text-sm text-slate-400">ريال/سنة من عميل واحد</div>
</div>
<div className="rounded-xl border border-white/10 bg-white/5 p-4">
<div className="text-3xl font-extrabold text-amber-400">74,400</div>
<div className="mt-1 text-sm text-slate-400">ريال/سنة من 5 عملاء</div>
</div>
<div className="rounded-xl border border-white/10 bg-white/5 p-4">
<div className="text-3xl font-extrabold text-amber-400">223,800</div>
<div className="mt-1 text-sm text-slate-400">ريال/سنة كشريك نشط</div>
</div>
</div>
<p className="mt-4 text-sm text-slate-500">
بناءً على: setup 3,000 ريال + 990 ريال/شهر × 30% نصيبك
</p>
</div>
</section>
{/* ── Trust / Proof ── */}
<section className="py-16">
<div className="mx-auto max-w-3xl px-6">
<div className="grid gap-4 sm:grid-cols-2">
{[
{ icon: Shield, text: "متوافق مع نظام حماية البيانات (PDPL)" },
{ icon: Zap, text: "مبني للسوق السعودي — عربي أولاً" },
{ icon: Clock, text: "بدون عقد سنوي — شهري" },
{ icon: CheckCircle, text: "ضمان استرداد 30 يوم" },
].map((item) => (
<div
key={item.text}
className="flex items-center gap-3 rounded-xl border border-white/10 bg-white/5 p-4"
>
<item.icon className="h-5 w-5 shrink-0 text-emerald-400" />
<span className="text-sm text-slate-300">{item.text}</span>
</div>
))}
</div>
</div>
</section>
{/* ── FAQ ── */}
<section className="border-y border-white/10 bg-slate-900/50 py-16">
<div className="mx-auto max-w-3xl px-6">
<h2 className="flex items-center justify-center gap-2 text-2xl font-bold">
<HelpCircle className="h-6 w-6 text-amber-400" />
أسئلة شائعة
</h2>
<div className="mt-8 space-y-4">
{faqs.map((faq) => (
<div
key={faq.q}
className="rounded-xl border border-white/10 bg-white/5 p-5"
>
<h3 className="font-semibold text-white">{faq.q}</h3>
<p className="mt-2 text-sm text-slate-400">{faq.a}</p>
</div>
))}
</div>
</div>
</section>
{/* ── Final CTA ── */}
<section className="py-20">
<div className="mx-auto max-w-2xl px-6 text-center">
<h2 className="text-3xl font-extrabold">
ابدأ بأول عميل {" "}
<span className="text-amber-400">مجاناً</span>
</h2>
<p className="mt-4 text-lg text-slate-300">
نسوي pilot 7 أيام لأول عميل عندك بدون تكلفة.
<br />
لو اشتغل تبدأ تحصّل. لو ما اشتغل لا التزام.
</p>
<div className="mt-8 flex flex-wrap justify-center gap-4">
<a
href={CALENDLY}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-xl bg-amber-500 px-8 py-4 text-lg font-bold text-slate-900 transition hover:bg-amber-400"
>
<Calendar className="h-5 w-5" />
احجز مكالمة شراكة
</a>
</div>
<p className="mt-4 text-sm text-slate-500">
20 دقيقة بدون التزام نسوي pilot لأول عميل مجاناً
</p>
</div>
</section>
{/* ── Footer nav ── */}
<footer className="border-t border-white/10 py-8">
<div className="mx-auto flex max-w-4xl flex-wrap items-center justify-center gap-6 px-6 text-sm text-slate-500">
<Link href="/" className="hover:text-teal-400">
الرئيسية
</Link>
<Link href="/resources" className="text-sm text-teal-400 hover:text-teal-300">
<Link href="/resources" className="hover:text-teal-400">
الموارد
</Link>
<Link href="/dashboard" className="text-sm text-teal-400 hover:text-teal-300">
<Link href="/dashboard" className="hover:text-teal-400">
المنصة
</Link>
<Link href="/privacy" className="hover:text-teal-400">
الخصوصية
</Link>
<Link href="/terms" className="hover:text-teal-400">
الشروط
</Link>
<span>© {new Date().getFullYear()} Dealix</span>
</div>
</div>
</footer>
</div>
);
}

View File

@ -0,0 +1,176 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dealix — جرّب مندوب المبيعات الذكي مجاناً</title>
<meta name="description" content="ديلكس يرد على عملائك بالعربي 24/7، يأهلهم، ويحجز المواعيد. جرّب 7 أيام مجاناً.">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Segoe UI',Tahoma,sans-serif;background:#0f172a;color:#fff;min-height:100vh}
.hero{max-width:700px;margin:0 auto;padding:40px 20px;text-align:center}
.badge{display:inline-block;background:rgba(245,158,11,.15);color:#f59e0b;padding:6px 16px;border-radius:20px;font-size:14px;margin-bottom:24px;border:1px solid rgba(245,158,11,.3)}
h1{font-size:clamp(28px,5vw,48px);line-height:1.3;margin-bottom:16px;font-weight:800}
h1 span{background:linear-gradient(to left,#f59e0b,#f97316);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.sub{font-size:18px;color:#94a3b8;margin-bottom:32px;line-height:1.6}
.form-box{background:#1e293b;border-radius:16px;padding:32px;text-align:right;max-width:500px;margin:0 auto}
.form-box h2{font-size:22px;margin-bottom:8px}
.form-box p{color:#94a3b8;font-size:14px;margin-bottom:24px}
label{display:block;font-size:14px;color:#cbd5e1;margin-bottom:6px;margin-top:16px}
input,select,textarea{width:100%;padding:12px;border-radius:8px;border:1px solid #334155;background:#0f172a;color:#fff;font-size:16px}
input:focus,select:focus,textarea:focus{outline:none;border-color:#f59e0b}
textarea{height:80px;resize:vertical}
.btn{display:block;width:100%;padding:14px;background:#f59e0b;color:#0f172a;border:none;border-radius:10px;font-size:18px;font-weight:700;cursor:pointer;margin-top:24px;transition:.2s}
.btn:hover{background:#fbbf24}
.btn:disabled{opacity:.5;cursor:not-allowed}
.trust{display:flex;gap:16px;justify-content:center;margin-top:24px;font-size:13px;color:#64748b;flex-wrap:wrap}
.success{display:none;text-align:center;padding:40px}
.success h2{color:#10b981;font-size:28px;margin-bottom:16px}
.success p{color:#94a3b8;font-size:16px;line-height:1.8}
.success a{color:#f59e0b;text-decoration:none;font-weight:700;font-size:20px;display:inline-block;margin-top:20px;padding:14px 32px;background:rgba(245,158,11,.15);border-radius:12px;border:1px solid rgba(245,158,11,.3)}
.stats{display:flex;gap:24px;justify-content:center;margin:32px 0;flex-wrap:wrap}
.stat{text-align:center}
.stat .num{font-size:32px;font-weight:800;color:#f59e0b}
.stat .lbl{font-size:13px;color:#64748b;margin-top:4px}
.features{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin:32px 0;text-align:right}
.feat{background:#1e293b;padding:16px;border-radius:12px}
.feat h3{font-size:15px;margin-bottom:6px;color:#f59e0b}
.feat p{font-size:13px;color:#94a3b8;line-height:1.5}
</style>
</head>
<body>
<div class="hero">
<div class="badge">🇸🇦 صُنع للسوق السعودي • 7 أيام مجاناً</div>
<h1>موظف مبيعات ذكي<br><span>يقفل لك الصفقات وأنت نايم</span></h1>
<p class="sub">يرد على عملائك بالعربي خلال 45 ثانية، يأهلهم، يحجز المواعيد، ويجهّز فريقك للإقفال.</p>
<div class="stats">
<div class="stat"><div class="num">45</div><div class="lbl">ثانية متوسط الرد</div></div>
<div class="stat"><div class="num">24/7</div><div class="lbl">بدون توقف</div></div>
<div class="stat"><div class="num">73%</div><div class="lbl">عملاء يتوقعون رد سريع</div></div>
</div>
<div class="features">
<div class="feat"><h3>رد فوري بالعربي</h3><p>يرد على واتساب وإنستقرام والموقع بلهجة سعودية طبيعية</p></div>
<div class="feat"><h3>تأهيل ذكي</h3><p>يسأل أسئلة التأهيل الصحيحة ويفرز الجادين</p></div>
<div class="feat"><h3>حجز تلقائي</h3><p>يحجز المواعيد مباشرة في تقويمك</p></div>
<div class="feat"><h3>تقارير يومية</h3><p>يعطيك تقرير مختصر عن كل فرصة وتقدّم</p></div>
</div>
<div id="formSection" class="form-box">
<h2>ابدأ تجربة مجانية 7 أيام</h2>
<p>بدون بطاقة ائتمان • بدون التزام • إلغاء أي وقت</p>
<form id="leadForm">
<label>الاسم الكامل *</label>
<input type="text" name="name" required placeholder="مثال: سامي العسيري">
<label>اسم الشركة *</label>
<input type="text" name="company" required placeholder="مثال: شركة النمو للتقنية">
<label>البريد الإلكتروني *</label>
<input type="email" name="email" required placeholder="sami@company.com">
<label>رقم الجوال (واتساب) *</label>
<input type="tel" name="phone" required placeholder="+966 5XX XXX XXXX" dir="ltr">
<label>القطاع</label>
<select name="sector">
<option value="">اختر القطاع</option>
<option value="saas">SaaS / تقنية</option>
<option value="ecommerce">تجارة إلكترونية</option>
<option value="realestate">عقارات</option>
<option value="fnb">مطاعم / أغذية</option>
<option value="services">خدمات B2B</option>
<option value="agency">وكالة تسويق / إعلان</option>
<option value="logistics">لوجستيك / توصيل</option>
<option value="education">تعليم / تدريب</option>
<option value="healthcare">صحة / طب</option>
<option value="fintech">تقنية مالية</option>
<option value="other">قطاع آخر</option>
</select>
<label>كم lead تستقبلون شهرياً (تقريبياً)؟</label>
<select name="leads_monthly">
<option value="">اختر</option>
<option value="0-50">أقل من 50</option>
<option value="50-200">50 - 200</option>
<option value="200-500">200 - 500</option>
<option value="500+">أكثر من 500</option>
</select>
<label>أكبر تحدي في المبيعات حالياً</label>
<textarea name="challenge" placeholder="مثال: الرد بطيء على العملاء، متابعة ضعيفة..."></textarea>
<button type="submit" class="btn" id="submitBtn">ابدأ التجربة المجانية →</button>
</form>
<div class="trust">
<span>✓ بدون بطاقة ائتمان</span>
<span>✓ إعداد 10 دقائق</span>
<span>✓ إلغاء أي وقت</span>
<span>✓ متوافق PDPL</span>
</div>
</div>
<div id="successSection" class="success">
<h2>تم التسجيل بنجاح! 🎉</h2>
<p>شكراً <strong id="userName"></strong>!<br>
فريقنا بيتواصل معك خلال ساعتين على واتساب لبدء الإعداد.<br><br>
أو احجز موعد Demo مباشرة:</p>
<a href="https://calendly.com/sami-assiri11/dealix-demo" target="_blank">📅 احجز Demo الآن</a>
</div>
</div>
<script>
const form = document.getElementById('leadForm');
const formSection = document.getElementById('formSection');
const successSection = document.getElementById('successSection');
const submitBtn = document.getElementById('submitBtn');
form.addEventListener('submit', async (e) => {
e.preventDefault();
submitBtn.disabled = true;
submitBtn.textContent = 'جاري التسجيل...';
const data = Object.fromEntries(new FormData(form));
data.source = 'landing_page';
data.timestamp = new Date().toISOString();
data.page_url = window.location.href;
// Try Railway backend
try {
await fetch('https://web-dealix.up.railway.app/api/v1/prospect/demo', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
} catch(e) { /* silent — manual follow-up */ }
// Log to PostHog if available
try {
if (window.posthog) {
window.posthog.capture('lead_captured', data);
}
} catch(e) {}
// Store locally as backup
const leads = JSON.parse(localStorage.getItem('dealix_leads') || '[]');
leads.push(data);
localStorage.setItem('dealix_leads', JSON.stringify(leads));
// Send notification email via mailto fallback
const subject = encodeURIComponent(`Dealix Lead: ${data.company} - ${data.name}`);
const body = encodeURIComponent(
`New Lead!\n\nName: ${data.name}\nCompany: ${data.company}\nEmail: ${data.email}\nPhone: ${data.phone}\nSector: ${data.sector}\nLeads/mo: ${data.leads_monthly}\nChallenge: ${data.challenge}\nTime: ${data.timestamp}`
);
// Show success
document.getElementById('userName').textContent = data.name.split(' ')[0];
formSection.style.display = 'none';
successSection.style.display = 'block';
// Open mailto as notification (optional)
// window.open(`mailto:sami.assiri11@gmail.com?subject=${subject}&body=${body}`);
});
</script>
</body>
</html>