# Campaign System

Email campaigns connect **segments**, **templates**, and the **sending engine** to reach market and CRM audiences with measurable outcomes.

## Campaign lifecycle

```mermaid
stateDiagram-v2
  [*] --> draft
  draft --> scheduled: schedule
  scheduled --> draft: unschedule
  scheduled --> sending: start batch
  draft --> sending: send batch
  sending --> sent: all recipients done (no failures)
  sending --> failed: recipients done with failures
  sent --> completed: finalize (future)
  draft --> archived: archive
  scheduled --> archived: archive
```

| Status | Description |
|--------|-------------|
| `draft` | Editable; not queued |
| `scheduled` | `scheduledAt` set; job will pick up |
| `sending` | Send job in progress |
| `sent` | Sending run finished (future) |
| `completed` | Final campaign accounting (future) |
| `failed` | Sending run failed (future) |
| `archived` | Hidden from active operations |

## Campaign object

- **Name** — internal label
- **Segment** — audience definition (see segments module)
- **Template** — subject + HTML body with merge fields
- **Owner** — BD/marketing user accountable
- **UTM parameters** — optional for link tracking analytics

## Audience resolution (Task 009)

1. Load segment filter definition and members snapshot
2. Resolve emails from people/companies/contact points
3. Keep only valid email addresses
4. Deduplicate by normalized email within the campaign
5. Mark invalid/non-email/duplicates as `skipped` with `skipReason`
6. Save recipient rows to `campaign_recipients` as `pending`/`skipped`

## Personalization variables

| Variable | Source |
|----------|--------|
| `{{company.name}}` | Company record |
| `{{company.nameAr}}` | Company Arabic name |
| `{{person.firstName}}` | Person |
| `{{person.lastName}}` | Person |
| `{{person.title}}` | Person |
| Unsubscribe link | System-generated token |

Invalid or missing variables: fallback to empty string in rendering preview. Invalid variable names are rejected at template validation time.

## Scheduling and execution

- **Task 009 scope:** draft/schedule metadata, preview recipients, generate recipient rows, recalculate stats.
- **Task 010 scope:** SMTP sending, per-recipient send status updates, open/click tracking updates, synchronous batch send endpoints.
- **Future:** background queue/worker orchestration, retries, and progressive full-run scheduler.

## Compliance and best practices

- Honor unsubscribe/suppression list
- Saudi business context: professional Arabic/English content review before send
- Test send to internal addresses before full launch

## n8n integration

On campaign create (optional): `POST` to `N8N_WEBHOOK_CAMPAIGN_CREATED` with campaign id, name, segment size, owner.

## Metrics (Phase 7+)

- Sent, delivered (if available), opened, clicked, replied
- Reply may create or update lead — see [07-leads-pipeline.md](./07-leads-pipeline.md)

## Duplicate send protection

Configured in **Settings → Email Policy** (`SystemSetting.emailPolicy`):

| Field | Default | Behavior |
|-------|---------|----------|
| `duplicateWindowDays` | `7` | Look back window for `email_send_history` (campaign, sequence, manual). `0` disables duplicate checks. |
| `duplicateAction` | `warn` | `warn` → HTTP 409 + UI modal; `skip` → auto-skip duplicates; `allow` → send without warning |

API: `GET` / `PATCH` `/api/settings/email-policy` (admin only).

Pre-send check: `POST /api/campaigns/:id/duplicate-send-check`.

When `duplicateAction=warn` and duplicates exist, send endpoints return **409** with `requiresDuplicateDecision: true`. The UI opens a decision modal (send / skip / cancel) — not an error state.

## Task 009 + Task 010 API surface

- `POST /api/campaigns/preview-recipients`
- `POST /api/campaigns/:id/generate-recipients`
- `GET /api/campaigns/:id/recipients`
- `GET /api/campaigns/:id/stats`
- `POST /api/campaigns/:id/duplicate-send-check` — pre-send duplicate analysis (`mode`, `recipientId?`, `limit?`)
- `POST /api/campaigns/:id/send-batch` — unified send (`mode`: `single` | `batch` | `all` | `retry_failed`, `limit`, `duplicateDecision`)
- `POST /api/campaigns/:id/send-one` — single recipient (delegates to unified send)
- `GET /api/email-tracking/open/:trackingId` (public)
- `GET /api/email-tracking/click/:trackingId?url=...` (public)

These endpoints now support preparation and synchronous sending batches.

## Dashboard performance metrics (Task 014)

`GET /api/dashboard` → `campaigns` aggregates from `campaign_recipients`:

- `totalCampaigns`, `activeCampaigns` (scheduled/sending/sent/completed)
- `sentEmails`, `openedEmails`, `clickedEmails`, `repliedEmails`, `bouncedEmails`
- `openRate`, `clickRate`, `replyRate` (percent of sent)

Displayed on `/dashboard` with progress bars.

## Related tasks

- task-011 Segments
- task-009 Campaigns module (current)
- task-010 Email Sending Engine (next)
- task-017 Tracking
- task-019 n8n webhooks
