# Email Campaign System — User / Operations Guide

Short day-to-day guide. For install/local/deploy see [`README.md`](../README.md).

## Workflow

```
Upload contacts (sender + receiver, CSV) → Create template → Create & start campaign
   → every 2 min scheduler drips a batch (respecting daily cap)
   → recipients sent / failed / bounced
   → unsubscribe / spam / confirm links update lists automatically
```

1. **Upload** sender & receiver lists — `Contacts ▸ Upload` (receiver list can get a
   **Purpose** label for targeting).
2. **Create** a template — `Templates` (subject + HTML; plain-text auto-generated).
3. **Create & start** a campaign — pick sender + template + audience.
4. Watch the **Dashboard** and campaign page; review **Reports**.

## Contacts

- CSV header `email,name` (`name` optional), max 5MB, `.csv`/`.txt`.
- Receiver uploads may carry a **Purpose** tag; keep the string identical across uploads
  (e.g. always `newsletter`).
- Suppressed addresses are skipped automatically; import summary shows
  Imported / Skipped / Invalid / Total.
- Use senders you actually authenticate on the relay — fake domains get rejected.

## Templates

- **Tokens** (replaced per recipient):

  | Token | Replaced with |
  |---|---|
  | `{{ $name }}` | Contact name |
  | `{{ $email }}` | Contact email |
  | `{{ $unsubscribeUrl }}` | Signed one-click unsubscribe link |
  | `{{ $confirmUrl }}` | Signed opt-in confirm link |
- A compliant footer (unsubscribe line + sender sign-off + site URL) is added
  **automatically** to every email — you don't need to add one.

## Campaigns

- **Audience**: all active receivers (empty purpose) · a specific **purpose** · or
  **specific emails**. Both purpose+emails = only matching. Suppressed always excluded.
- **Start** → `running` (scheduler picks it up); **pause** stops dispatch; completed
  campaigns can't restart.
- Page live-refreshes progress: pending / sent / failed / bounced.

## How sending works

Every 2 min, `CampaignDispatchService`:
1. Stops if the **daily cap** is reached (`WarmupService::canSendMore`).
2. Batch size = remaining quota ÷ remaining 2-min slots, clamped 1–20.
3. Pulls `pending` recipients (active, non-suppressed contacts).
4. Queues `SendCampaignEmailJob` per recipient → success = `sent` + count; failure = `failed`.

Each email gets `List-Unsubscribe` (one-click), `Precedence: bulk`,
`X-Auto-Response-Suppress`, and signed links.

## Opt-in, unsubscribe & spam (public signed routes)

| Route | Effect |
|---|---|
| `GET /unsubscribe/{contact}` | Suppress + mark contact `unsubscribed` |
| `GET /confirm/{contact}` | Stamp `confirmed_at` (opt-in) |
| `GET /spam/{contact}` | Record spam report **and** suppress |

Once suppressed, an address is never selected as a recipient again.

## Reports & dashboard

- **Dashboard**: Today's Cap · Sent Today · Warm-up Day · Overall Bounce Rate.
- **Reports**: subscription / unsubscription / spam tallies, all campaigns, and a
  per-campaign page with every recipient and status counts.

## Warm-up caps (seeded)

| Day | 1 | 2 | 3 | 4 | 5 | 6 | 7+ |
|---|---|---|---|---|---|---|---|
| Cap | 50 | 150 | 400 | 800 | 1500 | 3000 | `CAMPAIGN_STEADY_STATE_DAILY_CAP` (5000) |

## Monitoring

- **Logs**: `storage/logs/laravel.log` — dispatch + send errors (recipient id + SMTP error).
- **Bounces**: `bounces:check` every 15 min (only if `CAMPAIGN_BOUNCE_MAILBOX` is set).
- **Queue**: needs a running worker. If it's down, jobs are dispatched but not sent.

## Troubleshooting

| Symptom | Fix |
|---|---|
| `535 Incorrect authentication data` | Quote `MAIL_PASSWORD` (a `#` truncates it). |
| Emails in spam | Fix/provide real links; set SPF/DKIM/DMARC; ensure footer resolves. |
| Link 404s | `APP_URL` must match the real public base URL. |
| No sends | Daily cap reached, or no queue worker running. |
| Recipient stuck `pending` | Campaign paused / contact not active / suppressed. |

## Compliance reminders

Send only to permission-based lists; keep purposes meaningful; honor unsubscribes
immediately; act on spam reports; authenticate the sender domain (SPF/DKIM/DMARC).
