# System Architecture

## High-Level Overview

```
+----------------+      +-------------------+      +------------------+
|   User's       |      |   Next.js SPA     |      |   Laravel API   |
|   Browser      |◄────►|   (Vercel)        |◄────►|   (VPS/Hosting) |
+----------------+      +-------------------+      +------------------+
                                                          │
                                                          │ DB queries
                                                          ▼
                                                +----------------------+
                                                |   MySQL/PostgreSQL   |
                                                +----------------------+
                                                          │
                                                          │ Job dispatch
                                                          ▼
                                                +----------------------+
                                                |   Laravel Queues    |
                                                |   (Database driver)  |
                                                +----------------------+
                                                          │
                                                          │ Process job
                                                          ▼
                                                +----------------------+
                                                |   OpenWA Service    |
                                                |   (Mac Mini)        |
                                                +----------------------+
                                                          │
                                                          │ WhatsApp message
                                                          ▼
                                                +----------------------+
                                                |      WhatsApp        |
                                                +----------------------+
```

## Components Detail

### 1. Frontend (Next.js)

**Hosting**: Vercel (auto-SSL, CDN, free tier)

**Tech**:
- React 19 + TypeScript
- Tailwind CSS + shadcn/ui
- NextAuth.js for authentication (or Laravel Sanctum tokens)
- React Query for API state

**Pages**:
- `/auth/login`, `/auth/register`
- `/dashboard` (overview: sessions, campaigns, stats)
- `/sessions` (manage WhatsApp numbers)
- `/campaigns` (list, create, edit campaigns)
- `/contacts` (import, manage contact lists)
- `/billing` (subscription, usage)

**Key flows**:
1. User logs in → JWT stored → API calls with `Authorization: Bearer <token>`
2. Add session → GET `/api/sessions/qr` → show QR modal → user scans → polling until active
3. Create campaign → upload CSV → map columns → compose message → schedule → job queued
4. View campaign results → GET `/api/campaigns/{id}/messages` → see status per recipient

### 2. Backend (Laravel 11)

**Hosting**: Namecheap shared hosting or VPS (DigitalOcean/Linode)

**Core Packages**:
- `laravel/sanctum` - API token authentication
- `laravel/queues` - job processing (database or Redis)
- `stripe/stripe-php` - billing
- `intervention/image` - image processing for media messages
- `spatie/laravel-permission` - role/permission management (future admin panel)

**Database**: MySQL 8+ (or PostgreSQL if you prefer)

**Key Services**:

#### AuthService
- Register/login with email/password
- JWT tokens via Sanctum
- Password reset via email

#### SessionManager
```php
class SessionManager {
    public function createSession(User $user): WaSession {
        // Generate unique session ID
        $sessionId = Str::uuid();
        // Prepare OpenWA session directory
        $sessionPath = storage_path("app/sessions/{$user->id}_{$sessionId}");
        // Start OpenWA process (via CLI) with that session dir
        $this->startOpenwaSession($sessionPath, $sessionId);
        // Return session record with QR code path
        return WaSession::create([...]);
    }

    public function getQrCode(WaSession $session): string {
        // Read QR from OpenWA's session directory
        return file_get_contents("$session->path/qr.png");
    }

    public function checkHealth(WaSession $session): SessionHealth {
        // Query OpenWA via webhook or API to get status
        // Return: connected, disconnected, banned, qr_ready, etc.
    }

    public function sendMessage(WaSession $session, string $to, string $text, ?string $mediaUrl): MessageResult {
        // Call OpenWA's REST API endpoint (or CLI command) to send
        // Return: success, messageId, timestamp, error if any
    }
}
```

#### CampaignScheduler
```php
class CampaignScheduler {
    public function schedule(Campaign $campaign) {
        // Validate: user has enough quota, contacts valid
        // For each contact, create Message record with status 'pending'
        // Dispatch SendCampaignJob with delay = scheduled_at
    }
}

class SendCampaignJob implements ShouldQueue {
    public function handle() {
        // Get campaign
        // For each pending message (in chunks):
        //   Rate limit: sleep(1-2s) between sends
        //   Call SessionManager::sendMessage()
        //   Update Message record with status (sent/failed) and logs
        //   If failed due to session issue, trigger reconnection workflow
    }
}
```

#### BillingService
```php
class BillingService {
    public function checkQuota(User $user): bool;
    public function incrementMessageCount(User $user, int $count);
    public function handleStripeWebhook($payload);
    public function createSubscription(User $user, string $priceId);
    public void downgradeOnCancellation(User $user);
}
```

### 3. OpenWA Integration (Mac Mini)

**Process Management**: PM2

**OpenWA Instances**: One per active user session (each with its own data directory)

**Directory Structure on Mac Mini**:
```
/opt/openwa/
├── sessions/
│   ├── user_1_session_a1b2c3/     # OpenWA data (cookies, localStorage)
│   │   ├── qr.png
│   │   ├── auth.json
│   │   └── ...
│   └── user_2_session_d4e5f6/
├── logs/
│   ├── user_1.log
│   └── user_2.log
└── managers/
    └── manager-cli.php            # CLI tool to manage sessions
```

**Session Lifecycle**:
1. User requests QR → API calls `SessionManager::createSession()` → spawns OpenWA with `--session=sessions/{userId}_{sessionId}`
2. OpenWA writes QR to `qr.png` in session dir
3. API returns QR image URL (or base64)
4. User scans → OpenWA connects → webhook notifies our Laravel app (`POST /webhooks/openwa/status`)
5. Session status updated to `active`
6. Campaigns can now send using this session

**Rate Limiting**: Implemented in `SendCampaignJob` (sleep 1.5s between messages). OpenWA itself may enforce limits; respect them.

**Health Checking**: `CheckSessionHealthJob` runs every 5 minutes:
- Polls OpenWA for session status (via its API or by reading log)
- If `disconnected` or `banned`, mark session inactive and notify user via email
- If `qr_ready` (needs re-scan), notify user

**Session Cleanup**:
- When user deletes session → send `DELETE` to OpenWA API (if available) or kill process
- Remove session directory after 7 days grace period (cron)
- Archive logs

### 4. Database Schema (See `docs/database-schema.md`)

Key tables:
- `users`
- `wa_sessions`
- `contacts`
- `campaigns`
- `campaign_messages`
- `subscriptions`
- `message_logs` (audit)

### 5. Security Model

**Per-user isolation**:
- All queries scoped by `user_id` (except admin)
- Session data encrypted at rest using Laravel's encryption
- Media files stored with user-specific paths

**API security**:
- Sanctum tokens with 1-year expiry, revocable
- Rate limiting: 60 req/min per user
- HTTPS only (Cloudflare tunnel enforces)

**OpenWA security**:
- Each session isolated by separate data directory
- No cross-user access possible
- Process runs under its own user (if possible) or controlled by PM2

### 6. Monitoring & Alerting

**Internal**:
- Laravel Telescope (optional for debug)
- Log all job outcomes to `message_logs`

**External**:
- UptimeRobot or similar to ping `/health` endpoint
- Email alerts when:
  - Session disconnects
  - Campaign fails > 50% of messages
  - Stripe payment fails

**User notifications**:
- Email: campaign started/completed, session disconnected, payment failed
- In-app notifications (database table `notifications`)

## Scalability Considerations

**Current**: Single Mac mini can handle ~50-100 concurrent OpenWA sessions (RAM/CPU). Limit users accordingly or scale horizontally.

**Scale strategy**:
- Add more Mac minis as "workers"
- Backend API becomes load-balanced
- Sessions assigned to workers via `worker_id` column
- Queue jobs target specific worker

**Alternative**: Run OpenWA in Docker containers on a larger server (more sessions per machine).

## Failure Modes

| Failure | Detection | Recovery |
|---------|-----------|----------|
| OpenWA process dies | Health check job | Auto-restart via PM2, notify user |
| QR expired (user didn't scan) | Session status `qr_expired` | Generate new QR, notify user |
| WhatsApp banned number | Webhook `session.banned` | Mark session dead, email user, suggest new number |
| Rate limit hit | Send error `rate_limited` | Job retries later, alert user to slow down |
| Out of quota | BillingService::checkQuota() | Block sending, prompt upgrade |
| Payment failed | Stripe webhook | Suspend sending, remind user to update payment |

## Development Workflow

1. Backend changes → test with PHPUnit (minimal) → migrate DB → deploy
2. Frontend changes → build → Vercel auto-deploys
3. OpenWA integration → test locally with mock → update manager script → PM2 reload

## Deployment Checklist

- [ ] Domain DNS points to Cloudflare
- [ ] Cloudflare tunnel created and authenticated
- [ ] Backend deployed and `.env` configured
- [ ] Database migrated
- [ ] OpenWA running on Mac mini with PM2
- [ ] Schedule CheckSessionHealthJob in Laravel scheduler
- [ ] Stripe webhook endpoint configured
- [ ] Email (SMTP) configured for notifications
- [ ] SSL certificates (automatic via Cloudflare)
- [ ] Monitoring/health checks set up
- [ ] Admin user created

## Open Questions (To Decide)

1. **Session storage**: Store entire OpenWA session blob in DB (encrypted) or keep on filesystem? Filesystem easier, but DB backup simpler if in DB. We'll do filesystem initially, backup via script.
2. **Queue driver**: Database (no extra infra) vs Redis (better performance). Start with database, migrate to Redis if >10k jobs/day.
3. **Media handling**: Store uploaded images in DB (base64) or filesystem? Filesystem, serve via signed URLs.
4. **Rate limit enforcement**: Per-user or per-session? Both: user has quota, session has inter-message gap.
5. **Webhooks**: How to expose OpenWA webhooks to Laravel? Through Cloudflare tunnel → Laravel route `/webhooks/openwa`.

---

Next: See `docs/database-schema.md` for the exact table definitions.