# Deployment Guide

This guide walks through setting up the WhatsApp Scheduler SaaS platform from scratch.

## Prerequisites

- Domain name (e.g., `wapiapi.yourdomain.com` for API, `app.yourdomain.com` for frontend)
- Cloudflare account (for tunnels and DNS)
- Mac mini (macOS) to run OpenWA (or Linux server)
- Namecheap shared hosting OR VPS (for Laravel backend)
- Stripe account (for billing)
- Basic familiarity with SSH, PHP, Composer, Node.js

## Step 1: Domain & DNS

1. Add your domain to Cloudflare
2. Create DNS records:
   - `api.yourdomain.com` → Cloudflare Tunnel (we'll create)
   - `app.yourdomain.com` → Vercel (CNAME) or your frontend host
   - Optional: `qr.yourdomain.com` → Cloudflare Tunnel to Mac mini for direct QR serving

## Step 2: Cloudflare Tunnel (Expose Backend)

On your Mac mini (or any machine with access to Laravel backend host):

```bash
# Install cloudflared
brew install cloudflare/cloudflare/cloudflared   # Mac
# Or download from cloudflare.com

# Authenticate with your Cloudflare account
cloudflared tunnel login

# Create a tunnel
cloudflared tunnel create whatsapp-api

# Configure the tunnel to point to your backend
# Edit the generated config file (~/.cloudflared/xxxx.json) or:
cloudflared tunnel route dns whatsapp-api api.yourdomain.com

# Create config.yml in the project: infrastructure/cloudflared/config.yml
# (see that file)

# Run the tunnel (as a service)
cloudflared service install whatsapp-api
sudo launchctl load /Library/LaunchDaemons/cloudflared.service.plist
# Or run manually: cloudflared tunnel run whatsapp-api
```

**Repeat** for OpenWA manager if you need direct access (optional). But ideally, Laravel talks to OpenWA via local socket on Mac mini; Cloudflare only exposes Laravel API.

---

## Step 3: Backend (Laravel) Setup on Namecheap

### Option A: Shared Hosting (cPanel)

Namecheap shared hosting usually supports PHP 8.x.

1. Create a new addon domain or subdomain: `api.yourdomain.com`
2. Upload Laravel project files via FTP/SFTP
3. Set document root to `public/`
4. Ensure `.env` is readable only by web server (chmod 400)
5. Install Composer dependencies:
   - Use SSH if available: `composer install --optimize-autoloader --no-dev`
   - Or run `composer install` locally, then upload `vendor/` (large)
6. Set up database:
   - In cPanel, create MySQL database + user
   - Import migrations: `php artisan migrate --force`
   - Seed optional: `php artisan db:seed --force`
7. Set environment variables in `.env`:
   ```
   APP_NAME="WAPI Scheduler"
   APP_ENV=production
   APP_KEY=base64:... (generate via `php artisan key:generate --show`)
   APP_DEBUG=false
   APP_URL=https://api.yourdomain.com

   LOG_CHANNEL=stack
   LOG_LEVEL=debug

   DB_CONNECTION=mysql
   DB_HOST=localhost
   DB_PORT=3306
   DB_DATABASE=your_db
   DB_USERNAME=your_user
   DB_PASSWORD=your_pass

   SANCTUM_STATEFUL_DOMAINS=api.yourdomain.com

   OPENWA_BASE_URL=http://localhost:2785
   OPENWA_API_KEY= # if you set one in OpenWA
   OPENWA_WEBHOOK_SECRET=super-secret-random-string

   STRIPE_KEY=pk_live_...
   STRIPE_SECRET=sk_live_...
   STRIPE_WEBHOOK_SECRET=whsec_...

   MAIL_MAILER=smtp
   MAIL_HOST=smtp.gmail.com
   MAIL_PORT=587
   MAIL_USERNAME=your_email@gmail.com
   MAIL_PASSWORD=your_app_password
   MAIL_ENCRYPTION=tls
   MAIL_FROM_ADDRESS=noreply@yourdomain.com
   MAIL_FROM_NAME="WAPI Scheduler"
   ```
8. Set up Laravel scheduler (cron):
   - In cPanel, add cron job: `* * * * * cd /home/username/api.yourdomain.com && php artisan schedule:run >> /dev/null 2>&1`
   - This runs every minute; our scheduled jobs (session health, reset monthly counters) are dispatched from there.

9. Configure storage linking (if you serve media from storage):
   - If shared hosting doesn't allow `symlink`, you may need to adjust: store media in `public/storage` manually or use S3.

10. Test: Visit `https://api.yourdomain.com/api/user` with Authorization header (use Postman first) to ensure API responds.

### Option B: VPS (DigitalOcean, Linode)

More control. Use Ubuntu 22.04 + Nginx + PHP-FPM.

1. Spin up droplet (2GB RAM minimum)
2. SSH, update: `sudo apt update && sudo apt upgrade`
3. Install: `nginx`, `php8.2-fpm php8.2-{mysql,pdo,curl,mbstring,xml,zip,bcmath}`, `composer`, `git`, `mysql-server`
4. Clone repo, `composer install --optimize-autoloader --no-dev`
5. Set up Nginx site config (see `infrastructure/docker/nginx/` for reference)
6. Set up MySQL database, run migrations
7. Configure `.env`
8. Set up cron for Laravel scheduler
9. Install SSL via Let's Encrypt (certbot)
10. Open firewall ports 80,443.

Then run Cloudflare tunnel to point to this VPS.

---

## Step 4: Mac Mini Setup (OpenWA)

1. Install Node.js 22 LTS: `brew install node@22`
2. Install PM2 globally: `npm install -g pm2`
3. Clone OpenWA to `~/openwa`:
   ```bash
   cd ~
   git clone https://github.com/rmyndharis/OpenWA.git
   cd OpenWA
   npm install
   npm run build
   ```
4. Configure `.env` in `~/openwa`:
   ```
   SESSION_DIR=/opt/openwa/sessions  # or ~/openwa/sessions
   WEBHOOK_URL=https://api.yourdomain.com/webhooks/openwa
   WEBHOOK_SECRET=the-same-secret-as-laravel
   PORT=2785
   ```
5. Create directories:
   ```bash
   mkdir -p /opt/openwa/sessions
   mkdir -p /opt/openwa/logs
   chmod -R 700 /opt/openwa/sessions
   ```
6. Test manually:
   ```bash
   cd ~/openwa
   npm start
   # OpenWA should start, print QR to stdout? Or write to session dir.
   # Check http://localhost:2785/api/sessions (if OpenWA has API)
   ```
7. Stop (Ctrl+C). We'll start via our manager script.

8. Install our manager:
   ```bash
   mkdir -p ~/openwa-integration/bin
   cp infrastructure/openwa-integration/* ~/openwa-integration/
   chmod +x ~/openwa-integration/bin/manage-openwa.php
   # Edit manage-openwa.php to set paths (OPENWA_ROOT, SESSIONS_DIR)
   ```
9. Start manager with PM2:
   ```bash
   cd ~/openwa-integration
   pm2 start bin/manage-openwa.php --interpreter php --name openwa-manager
   pm2 save
   pm2 startup   # to auto-start on boot
   ```

10. Check logs: `pm2 logs openwa-manager`

11. Test via Laravel API: register a user, add a session. Should see QR returned after few seconds.

---

## Step 5: Stripe Setup

1. Create Stripe account (if you don't have one)
2. Get API keys: `sk_live_...` and `pk_live_...`
3. In Stripe Dashboard:
   - **Products**: Create "Free", "Pro", "Business" with prices (monthly)
   - Copy `price_...` IDs to `.env` for reference (or store in DB configurable)
4. Set up webhook endpoint:
   - URL: `https://api.yourdomain.com/webhooks/stripe`
   - Select events:
     - `invoice.payment_succeeded`
     - `invoice.payment_failed`
     - `customer.subscription.updated`
     - `customer.subscription.deleted`
   - Copy the webhook signing secret (`whsec_...`) to `.env` as `STRIPE_WEBHOOK_SECRET`
5. Test in Stripe test mode first (use test keys).

---

## Step 6: Frontend (Next.js) Setup

1. Clone frontend repo (will be in `frontend/`)
2. Install Node.js 20+ on your machine (for building)
3. Configure environment:
   ```bash
   cp .env.local.example .env.local
   # Set:
   NEXT_PUBLIC_API_URL=https://api.yourdomain.com
   ```
4. Build: `npm run build`
5. Deploy to Vercel:
   ```bash
   npm i -g vercel
   vercel --prod
   ```
   Or push to GitHub and connect Vercel project.
6. Configure Vercel:
   - Environment variables in Vercel dashboard (same as `.env.local`)
   - Build command: `npm run build`
   - Output directory: `.next`
7. Deploy! Vercel gives you `your-project.vercel.app` or custom domain `app.yourdomain.com`.

8. Test: Visit frontend, register, login, add session.

---

## Step 7: Email Setup

You need transactional email for:
- Password reset
- Session disconnect alerts
- Campaign completion notifications
- Payment receipts

**Option A**: Gmail SMTP (simple but 2.5K/day limit)
- Use `MAIL_MAILER=smtp`, host `smtp.gmail.com`, port 587, app password.

**Option B**: Amazon SES (high volume, low cost)
- Verify domain, get SMTP credentials.

**Option C**: Postmark (good deliverability, paid)

Update `.env` accordingly.

---

## Step 8: Media Storage

User uploaded images for campaigns need to be publicly accessible for WhatsApp to fetch them.

**Option A: Local storage** (quick start):
- Store in `storage/app/public/media`
- Create symlink `public/storage` -> `storage/app/public` (Laravel's `php artisan storage:link`)
- But your Laravel backend is on Namecheap shared hosting - may not allow symlink.
- Workaround: Serve files via route with readfile and proper headers, but WhatsApp won't authenticate. Need public URL.
- So you might need to store in a publicly accessible location (like an S3 bucket with public-read objects).

**Option B: S3 with public-read** (recommended for production):
- Create AWS S3 bucket `your-wapa-media`
- Set CORS policy to allow public GET
- Objects ACL `public-read`
- In Laravel `.env`:
  ```
  AWS_ACCESS_KEY_ID=...
  AWS_SECRET_ACCESS_KEY=...
  AWS_DEFAULT_REGION=us-east-1
  AWS_BUCKET=your-wapa-media
  FILESYSTEM_DISK=s3
  ```
- Then `Storage::url($path)` returns `https://your-wapa-media.s3.amazonaws.com/xyz.jpg`
- Cost: cheap (~$0.023/GB).

**Enable in Laravel**: Install `league/flysystem-aws-s3-v3` via Composer, configure `config/filesystems.php`.

---

## Step 9: Admin Panel

Optional: Build a simple admin interface to manage users, view stats, pause accounts.

- Use Laravel Nova (paid) or Filament (free) to get admin dashboard quickly.
- Or custom routes with middleware `can:admin`.

For MVP, use Tinker or manual DB edits.

---

## Step 10: Monitoring & Alerts

1. **Uptime**: Use UptimeRobot to ping `https://api.yourdomain.com/api/health` every 5 minutes.
2. **Logs**:
   - Laravel logs: `storage/logs/laravel.log` (monitor with `pm2 logs` or logrotate)
   - OpenWA logs: `~/openwa/logs/*.log` - use `pm2 logs openwa-manager` or `logrotate`
   - Consider centralized logging (Papertrail) for easier access.
3. **Alerts** (email):
   - Laravel Telescope (local dev) or custom: if session disconnects, email admin.
   - Stripe payment failures → already via webhook, email user.

---

## Step 11: Test End-to-End

1. Register a test user.
2. Add a WhatsApp session with a **test phone number** (your personal WhatsApp).
3. Scan QR, ensure status becomes `connected`.
4. Create a contact with your own phone.
5. Create a campaign: text only, send now.
6. Verify message received on WhatsApp.
7. Check campaign report: status = sent/delivered.
8. Test image upload: attach a JPG, send, verify image arrives.
9. Simulate failure: disconnect OpenWA, schedule campaign, verify error handling (email notification).
10. Test billing: use Stripe test card, upgrade plan, verify quota enforcement.

---

## Step 12: Go Live Checklist

- [ ] Switch Stripe keys to live (`sk_live_...`)
- [ ] Set `APP_ENV=production` and `APP_DEBUG=false`
- [ ] Enable HTTPS on API and frontend (Cloudflare + SSL)
- [ ] Configure CORS on Laravel to allow only your frontend domain
- [ ] Rate limiting: ensure throttle middleware is on
- [ ] Set up daily database backups (Namecheap cPanel or VPS mysqldump cron)
- [ ] Set up backup of `/opt/openwa/sessions` (tar + encrypt, offsite)
- [ ] Update email FROM address to your domain
- [ ] Add Terms of Service and Privacy Policy pages to frontend, require acceptance on signup
- [ ] Add `robots.txt` to disallow indexing of private pages
- [ ] Review file permissions (no world-writable)
- [ ] Change all default passwords (MySQL, server SSH)
- [ ] Enable firewall on VPS (if using)

---

## Troubleshooting

### Cloudflare Tunnel not connecting
- Ensure `cloudflared` service is running
- Check tunnel credentials file `~/.cloudflared/xxxx.json` exists
- Verify DNS record in Cloudflare dashboard points to the tunnel
- Run `cloudflared tunnel info whatsapp-api` to see status

### Laravel .env not loading
- On shared hosting, sometimes you need to set `APP_KEY` directly in `public/index.php` if `.env` is ignored. Check hosting docs.
- Ensure `vendor/autoload.php` exists (Composer install completed).

### OpenWA process dies after PM2 reboot
- Set `pm2 save` and `pm2 startup` to resurrect
- Check `pm2 logs` for errors (missing env vars?)

### QR code not appearing in session
- Check OpenWA logs: maybe failed to initialize session directory
- Ensure directory permissions: `chmod 700` on sessions, owned by the user running OpenWA.
- Increase timeout in manager script (some phones take longer to generate QR).

### Messages not sending despite connected session
- Check `wa_sessions.status` is `connected`
- Check `users.messages_used_this_month < messages_limit`
- Check `campaign_messages.status` for errors
- Look at OpenWA logs for API errors (e.g., "media download failed" if image URL not public)
- Rate limiting: maybe need to increase gap

### Stripe webhook not received
- Ensure webhook URL is publicly accessible (use `curl -I https://api.yourdomain.com/webhooks/stripe`)
- Check Stripe dashboard: webhook endpoint logs show delivery attempts and errors.
- Verify Laravel route is defined without auth middleware.

---

## Rollback Plan

If something breaks:

1. API down? Switch Cloudflare tunnel to point to a static maintenance page.
2. OpenWA issue? Pause sending jobs (`php artisan queue:pause`), fix then resume.
3. Database corruption? Restore from daily backup.
4. Session data loss? Sessions are ephemeral; users can re-scan QR if needed.

---

## Cost Estimate (Monthly)

| Item | Cost |
|------|------|
| Namecheap shared hosting | $5-10 |
| VPS (DigitalOcean 2GB) | $24 |
| Cloudflare Tunnel | Free |
| Vercel (frontend) | Free |
| S3 storage (media) | $0.50/GB |
| Stripe fees | 2.9% + $0.30 per transaction |
| Your time | Priceless |

---

## Post-Deployment

- Add monitoring dashboard in admin panel
- A/B test pricing
- Collect user feedback
- Iterate on features: templates, analytics, webhooks

---

Good luck! Create issues in the repo if you get stuck.