# Email Campaigner — Scope & User Guide

**Version:** 1.0  
**Date:** 2026-04-15  
**App URL:** `http://localhost:8098/`  
**API URL:** `http://localhost:3002/`

---

## Table of Contents

1. [Project Overview](#1-project-overview)
2. [Technology Stack](#2-technology-stack)
3. [Feature Scope](#3-feature-scope)
4. [Screen-by-Screen User Guide](#4-screen-by-screen-user-guide)
   - [4.1 Login](#41-login)
   - [4.2 Dashboard](#42-dashboard)
   - [4.3 Campaigns](#43-campaigns)
   - [4.4 Campaign Detail](#44-campaign-detail)
   - [4.5 Contacts](#45-contacts)
   - [4.6 Lists](#46-lists)
   - [4.7 Templates](#47-templates)
   - [4.8 Analytics](#48-analytics)
   - [4.9 Transactional Emails](#49-transactional-emails)
   - [4.10 Settings](#410-settings)
   - [4.11 Users (Admin)](#411-users-admin)
5. [Database Schema](#5-database-schema)
6. [API Endpoints](#6-api-endpoints)
7. [Tracking & Compliance](#7-tracking--compliance)
8. [AI Enrichment & Review](#8-ai-enrichment--review)
9. [Reply Mailbox (Microsoft 365)](#9-reply-mailbox-microsoft-365)
10. [Campaign Builder (2026 refresh)](#10-campaign-builder-2026-refresh)

---

## 1. Project Overview

Email Campaigner is a self-hosted, web-based email marketing platform. It allows teams to manage contacts, build email templates, send bulk campaigns, track opens/clicks, and send one-off transactional emails — all from a single dashboard.

**Key capabilities:**

- Create and manage email campaigns with draft/sent lifecycle
- Import contacts via CSV and organise them into mailing lists
- Design email templates with a visual editor (GrapesJS) or MJML
- Send campaigns to targeted lists with batch processing
- Track opens, clicks, bounces, and unsubscribes per campaign
- Send transactional (one-off) emails outside of campaigns
- View analytics with per-campaign performance metrics
- Configure SMTP and sender defaults from the UI

---

## 2. Technology Stack

| Layer | Technology |
|-------|-----------|
| Frontend | React 18 + TypeScript, Vite, Tailwind CSS, React Router |
| API | Express (Node.js / TypeScript) |
| Database | PostgreSQL |
| Auth | Supabase (GoTrue) — JWT-based sessions |
| Gateway | Kong (API gateway) |
| Email | Nodemailer over SMTP (MailHog in dev) |
| Template Engine | GrapesJS (visual), MJML (code), Handlebars (merge fields) |
| Infrastructure | Docker Compose (all services) |

---

## 3. Feature Scope

### In Scope (Current)

| Feature | Status |
|---------|--------|
| Authentication (login / sign-up / sign-out / forgot password) | Implemented |
| Dashboard with KPI cards and recent campaigns | Implemented |
| Campaign CRUD (create, edit, view, delete) | Implemented |
| Campaign sending with batch processing | Implemented |
| Test email sends | Implemented |
| Contact management (CRUD + CSV import) | Implemented |
| List management (create, edit, assign contacts) | Implemented |
| Template editor (GrapesJS visual + MJML code) | Implemented |
| Open / click / bounce / unsubscribe tracking | Implemented |
| Analytics dashboard per campaign | Implemented |
| Transactional email sending | Implemented |
| App settings (SMTP, sender defaults) | Implemented |
| User management (admin-only) | Implemented |
| Dark / light theme toggle | Implemented |
| AI enrichment (LLM per recipient, human review, enriched send) | Implemented |
| Reply mailbox (Microsoft 365 IMAP, reply-to-unsubscribe, forwarding) | Implemented |

### Out of Scope (Future)

- Marketing automation / journey orchestration
- Fully autonomous bulk copy without an approval step (use enrichment + review instead)
- Advanced segmentation engine
- Scheduling / send-time optimisation
- Native mobile app

---

## 4. Screen-by-Screen User Guide

### 4.1 Login

![Login Page](screenshots/screenshot_01_login.png)

**URL:** `/login`

**What you see:**
- The Email Campaigner logo and app name in the top-left corner
- A centered card with the heading **"Welcome back"** and subtitle "Sign in to manage your email campaigns"
- **Email** input field (placeholder: `you@example.com`)
- **Password** input field with a show/hide toggle and **Forgot password?** (login mode only)
- **Sign In** button (blue, full-width)
- A "Don't have an account? **Sign up**" link below
- A theme toggle button (sun/moon icon) in the top-right corner
- Footer text: "Email Campaigner — Self-hosted email marketing"

**How to use:**
1. Enter your email address and password
2. Click **Sign In**
3. You will be redirected to the Dashboard on success
4. If you don't have an account, click **Sign up** to register

**Forgot password**
1. On the login card, click **Forgot password?**
2. Enter your account email and click **Send reset link** (GoTrue sends mail via configured SMTP, e.g. MailHog in dev)
3. Open the link in the email — it opens **`/reset-password`** on this app
4. Enter and confirm a new password, then **Update password**; you are signed in and redirected to the Dashboard

**Operator note:** In Supabase / GoTrue, add the exact redirect URL to **Redirect URLs** (e.g. `http://localhost:8098/reset-password` when using Docker). The app builds `redirectTo` from the current browser origin.

---

### 4.2 Dashboard

![Dashboard](screenshots/screenshot_02_dashboard.png)

**URL:** `/` (root, after login)

**What you see:**

**Sidebar (left):** Persistent navigation with links to all sections:
- Dashboard (highlighted when active)
- Campaigns
- Contacts
- Lists
- Templates
- Analytics
- Transactional
- Settings
- Logged-in user info at the bottom (email, role)
- Theme toggle and Sign out button

**Main area:**
- **Page heading:** "Dashboard"
- **Four KPI cards** across the top:
  - **Total Contacts** — count of all contacts in the system
  - **Total Campaigns** — count of all campaigns
  - **Avg. Open Rate** — average open rate across sent campaigns
  - **Avg. Click Rate** — average click-through rate
- **Sends — Last 7 Days** — a chart area showing recent send activity (displays "No send activity yet" when empty)
- **Recent Campaigns** — a list of the most recent campaigns with:
  - Campaign name
  - Send count and open percentage
  - Status badge (green **Sent** or grey **Draft**)

**How to use:**
- Use the Dashboard for a quick health check of your email operations
- Click any sidebar link to navigate to that section
- The KPI cards update automatically based on campaign data

---

### 4.3 Campaigns

![Campaigns List](screenshots/screenshot_03_campaigns.png)

**URL:** `/campaigns`

**What you see:**
- **Page heading:** "Campaigns"
- **+ New Campaign** button (top-right, blue) to create a campaign
- **Search bar** to filter campaigns by name
- **Campaign list** — each row shows:
  - Campaign name
  - Status badge (**Draft**, **Sent**, etc.)
  - Subject line, list name, and creation date
  - For sent campaigns: send count, open %, click %
  - **Action buttons** (right side of each row):
    - Eye icon — **View** campaign details
    - Pencil icon — **Edit** campaign
    - Paper plane icon — **Send** campaign
    - Red trash icon — **Delete** campaign

**How to use:**
1. Click **+ New Campaign** to start a new campaign
2. Fill in the campaign name, subject, sender details, pick a list and template
3. Use the search bar to find campaigns by name
4. Click the eye icon to view full campaign details
5. Click the pencil icon to edit a draft campaign
6. Click the paper plane icon to send a campaign

---

### 4.4 Campaign Detail

![Campaign Detail](screenshots/screenshot_11_campaign_detail.png)

**URL:** `/campaigns/:id`

**What you see:**
- **Back arrow** (top-left) to return to the campaigns list
- **Campaign name** as the page heading with a status badge (e.g. **Draft**)
- **Subject line** displayed below the name
- **Edit** button and **Send Campaign** button (top-right)
- **Four stat cards:**
  - **Recipients** — total number of recipients
  - **Sent** — number of emails delivered
  - **Opens** — unique open percentage and count
  - **Clicks** — unique click percentage and count
- **Campaign Info** section:
  - **From** — sender name and email
  - **List** — the contact list used
  - **Template** — the template used
  - **Bounces** — count of bounced emails
  - **Unsubscribes** — count of unsubscribes
- **Recipients** section — a table of individual recipients with their delivery status

**How to use:**
1. Review campaign stats after sending
2. Click **Edit** to modify the campaign (only available for drafts)
3. Click **Send Campaign** to deliver to all subscribed contacts on the list

---

### 4.5 Contacts

![Contacts](screenshots/screenshot_04_contacts.png)

**URL:** `/contacts`

**What you see:**
- **Page heading:** "Contacts"
- **Import CSV** button (top-right) for bulk import
- **+ Add Contact** button (top-right, blue)
- **Search bar** to filter contacts by email or name
- **Contact list** — each row shows:
  - Contact name (first + last)
  - Email address
  - Date added
  - **Unsubscribed** badge (red) if the contact has opted out
  - Red trash icon to **delete**
  - Arrow **>** to view contact details

**How to use:**
1. Click **+ Add Contact** to add a single contact manually
2. Click **Import CSV** to bulk-import contacts from a CSV file
3. Use the search bar to find contacts by name or email
4. Click the arrow on any contact row to see their full detail and campaign history
5. Contacts marked "Unsubscribed" will not receive future campaign emails

---

### 4.6 Lists

![Lists](screenshots/screenshot_05_lists.png)

**URL:** `/lists`

**What you see:**
- **Page heading:** "Lists"
- **+ New List** button (top-right, blue)
- **List cards** in a grid layout — each card shows:
  - List name
  - Description (if provided)
  - Contact count (e.g. "4 contacts")
  - Creation date
  - **Pencil icon** to edit the list name/description
  - **Red trash icon** to delete the list
  - **Add Contacts** link to assign contacts to the list

**How to use:**
1. Click **+ New List** to create a new mailing list
2. Give it a name and optional description
3. Click **Add Contacts** on a list card to assign contacts to it
4. When creating a campaign, you select one of these lists as the audience
5. Only subscribed contacts on the selected list will receive the campaign

---

### 4.7 Templates

![Templates](screenshots/screenshot_06_templates.png)

**URL:** `/templates`

**What you see:**
- **Page heading:** "Templates"
- **+ New Template** button (top-right, blue)
- **Template cards** in a grid layout — each card shows:
  - Template thumbnail (or a placeholder icon)
  - Template name
  - Last updated date
  - **Edit** button to open the template editor
  - **Red trash icon** to delete the template

**How to use:**
1. Click **+ New Template** to create a new email template
2. Choose between the **GrapesJS visual editor** (drag-and-drop) or **MJML code editor**
3. Click **Edit** on an existing template to modify it
4. Templates support **merge fields** using Handlebars syntax (e.g. `{{first_name}}`)
5. When creating a campaign, select a template to use for the email content

---

### 4.8 Analytics

![Analytics — Empty](screenshots/screenshot_07_analytics.png)

**URL:** `/analytics`

**What you see (before selecting a campaign):**
- **Page heading:** "Analytics"
- **Filter by campaign name** text field (top-right)
- **Campaign dropdown** to select a sent campaign
- Message: "Select a sent campaign to view analytics"

---

![Analytics — With Data](screenshots/screenshot_08_analytics_detail.png)

**What you see (after selecting a campaign):**
- **Four stat cards:**
  - **Sent** — total emails sent
  - **Unique Opens** — percentage and count of unique opens
  - **Unique Clicks** — percentage and count of unique clicks
  - **Bounces** — count of bounced emails
- **Events Over Time** — a line chart showing opens, clicks, and sends plotted over time
- Chart legend: Opens (green), Clicks (blue), Sent (grey)

**How to use:**
1. Select a sent campaign from the dropdown to view its analytics
2. Use the filter field to search campaigns by name
3. Review open/click rates and the timeline chart to understand engagement
4. Only campaigns with status "Sent" appear in the dropdown

---

### 4.9 Transactional Emails

![Transactional Emails](screenshots/screenshot_09_transactional.png)

**URL:** `/transactional`

**What you see:**
- **Page heading:** "Transactional Emails"
- **Send Email** button (top-right, blue)
- **Email history list** — each row shows:
  - Subject line
  - Recipient email address
  - Date and time sent
  - Status badge: **sent** (green) or **queued** (grey)

**How to use:**
1. Click **Send Email** to compose a one-off transactional email
2. Enter the recipient, subject, and body content
3. Optionally use Handlebars variables for personalisation
4. The email history shows all transactional sends and their delivery status
5. Transactional emails are separate from campaigns — use them for individual sends like welcome emails, receipts, etc.

---

### 4.10 Settings

![Settings](screenshots/screenshot_10_settings.png)

**URL:** `/settings`

**What you see:**
- **Page heading:** "Settings"
- **Save Settings** button (top, blue)
- **SMTP Configuration** section:
  - SMTP Host
  - SMTP Port
  - Username
  - Password
  - Info note: "In development mode, MailHog is always used regardless of SMTP settings. View caught emails at localhost:8025."
- **Sender Defaults** section:
  - Default From Name
  - Default From Email

**How to use:**
1. Configure your SMTP server details for production email delivery
2. Set the default "From" name and email address used when sending campaigns
3. Click **Save Settings** to persist changes
4. In development mode, all emails are captured by MailHog (accessible at `http://localhost:8025`)

---

### 4.11 Users (Admin)

![Users — Admin Required](screenshots/screenshot_12_users_admin.png)

**URL:** `/users`

**What you see:**
- A shield icon
- Heading: **"Admin access required"**
- Message: "Only administrators can manage users. Contact your admin to change your role."

**Admin features (when logged in as admin):**
- View all registered users
- Invite new users
- Change user roles (admin / user)
- Delete users

**How to use:**
- This page is restricted to users with the **admin** role
- Regular users see the "Admin access required" message
- Contact your administrator to gain access or to have your role upgraded

---

## 5. Database Schema

| Table | Purpose |
|-------|---------|
| `contacts` | Stores email addresses, names, custom fields, subscription status |
| `lists` | Named mailing lists |
| `list_contacts` | Many-to-many relationship between lists and contacts |
| `templates` | Email templates (GrapesJS JSON + compiled HTML) |
| `campaigns` | Campaign metadata, status, list/template associations |
| `campaign_recipients` | Per-recipient send state, tracking tokens, delivery timestamps |
| `email_events` | Event log (sent, opened, clicked, bounced, unsubscribed) |
| `tracked_links` | Rewritten URLs for click tracking per campaign |
| `tracked_link_clicks` | Individual click records with user agent and IP |
| `transactional_emails` | One-off transactional send log |
| `app_settings` | Key-value application settings (SMTP, sender defaults) |
| `profiles` | User profiles with roles (admin, user) |
| `campaign_settings` | Per-campaign AI tone, instructions, send-from overrides, reply-unsubscribe, forward/BCC lists |
| `campaign_enriched_emails` | Original vs LLM-enriched subject/body, review status, outbound Message-ID |
| `inbox_messages` | Inbound reply log, classification, forward audit |

---

## 6. API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Health check |
| GET/POST/PUT/DELETE | `/api/campaigns` | Campaign CRUD |
| POST | `/api/campaigns/:id/send` | Send a campaign |
| POST | `/api/campaigns/:id/test` | Send a test email |
| GET | `/api/campaigns/:id/recipients` | List campaign recipients |
| GET/POST/PUT/DELETE | `/api/contacts` | Contact CRUD |
| POST | `/api/contacts/import` | CSV import |
| GET/POST/PUT/DELETE | `/api/lists` | List CRUD |
| POST | `/api/lists/:id/contacts` | Add contacts to a list |
| GET/POST/PUT/DELETE | `/api/templates` | Template CRUD |
| POST | `/api/templates/compile` | Compile MJML to HTML |
| POST | `/api/transactional` | Send a transactional email |
| GET | `/api/transactional` | List transactional email history |
| GET | `/api/analytics` | Overview analytics |
| GET | `/api/analytics/:campaignId` | Per-campaign analytics |
| GET/PUT | `/api/settings` | Read/update app settings |
| GET | `/api/users` | List users (admin) |
| POST | `/api/users/invite` | Invite a user (admin) |
| GET | `/track/open/:token` | Open tracking pixel |
| GET | `/track/click/:token` | Click redirect + tracking |
| GET | `/track/unsubscribe/:token` | Unsubscribe page |
| GET | `/api/campaigns/:id/settings` | Campaign AI / mailbox settings |
| PUT | `/api/campaigns/:id/settings` | Upsert campaign settings |
| POST | `/api/campaigns/:id/enrich` | Start async per-recipient LLM enrichment |
| GET | `/api/campaigns/:id/enriched` | List enriched rows (`?status=` optional) |
| PUT | `/api/campaigns/:id/enriched/approve-all` | Approve all pending |
| PUT | `/api/campaigns/:id/enriched/reject-all` | Reject all pending |
| PUT | `/api/campaigns/:id/enriched/:emailId/approve` | Approve one row |
| PUT | `/api/campaigns/:id/enriched/:emailId/reject` | Reject one row |
| POST | `/api/campaigns/:id/send-enriched` | Send all approved enriched emails |
| GET | `/api/campaigns/:id/inbox` | Inbound messages logged for this campaign |

---

## 7. Tracking & Compliance

### Open Tracking
A transparent 1x1 pixel image is injected into each campaign email. When the recipient's email client loads the image, an "opened" event is recorded.

### Click Tracking
All links in campaign emails are rewritten to pass through the `/track/click/:token` endpoint. The system records the click event and redirects the recipient to the original URL.

### Unsubscribe
Each **standard** campaign email includes an unsubscribe link pointing to `/track/unsubscribe/:token`. When clicked, the contact's subscription status is set to "unsubscribed" and they are excluded from future campaign sends.

When **reply-based unsubscribe** is enabled on a campaign (`campaign_settings.reply_unsubscribe_enabled`), enriched sends **omit** the `List-Unsubscribe` header and rely on the recipient replying (e.g. “unsubscribe”). Inbound mail is processed by the API (IMAP poller): keywords are checked first, then an LLM classifies ambiguous text. The contact is marked unsubscribed and an auto-reply is sent.

### Compliance Notes
- Unsubscribed contacts are automatically excluded from sends
- List-Unsubscribe headers are included on standard campaign sends; omitted on enriched sends when reply-based unsubscribe is enabled
- Bounce events are recorded for delivery monitoring
- All tracking tokens are unique per recipient per campaign

---

## 8. AI Enrichment & Review

**Purpose:** Personalise each recipient’s email using OpenAI (default model `gpt-4o`) while keeping humans in the loop.

**Configuration**
- **New campaign dialog** — optional “AI enrichment & reply mailbox” block (tone, instructions, send-from, forward/BCC, unsubscribe reply).
- **Campaign detail** — “AI enrichment & mailbox” card with the same fields and **Save settings**.
- **Contacts** — `Enrichment data (JSON)` on add; full editor on **Contact detail** (“AI enrichment context”).

**Workflow**
1. Build the template in the **campaign builder** and **Save**.
2. Ensure **Enable AI enrichment** is on and settings are saved.
3. **Run AI enrich** (campaign detail or builder button). The API enqueues an async job (requires `OPENAI_API_KEY`).
4. Open **Review enrichment** (`/campaigns/:id/enrichment`): compare original vs enriched subject/body; **Approve** / **Reject** individually or in bulk.
5. **Send approved** — sends only rows in `approved` state via `MS365_SMTP_*` when configured, otherwise dev MailHog.

**Headers for threading:** Outbound sends store `Message-ID` on `campaign_enriched_emails` so replies can be correlated.

---

## 9. Reply Mailbox (Microsoft 365)

**Environment variables** (API process): `MS365_IMAP_HOST`, `MS365_IMAP_PORT` (default 993), `MS365_IMAP_USER`, `MS365_IMAP_PASSWORD`, optional `MAILBOX_POLL_SEC` (default 60).

**Behaviour**
- Polls the INBOX for **unseen** messages.
- **Unsubscribe intent** → mark contact `subscribed = false`, log `email_events`, send plaintext auto-reply (template from settings; optional LLM polish).
- **Other replies** → forward a summary to all addresses in `auto_forward_emails` for that campaign.
- Logs each processed message in `inbox_messages` (`GET /api/campaigns/:id/inbox`).

---

## 10. Campaign Builder (2026 refresh)

- **Toolbar:** Grouped actions, clearer hierarchy, compact icon buttons on small screens.
- **Merge fields:** Collapsible strip so the GrapesJS canvas gains vertical space.
- **Panels:** CSS tuning narrows GrapesJS right panels (~44px tab rail + ~176–208px panel) for a more professional, editor-first layout.
- **Enrich & review:** Visible when the campaign has **AI enrichment** enabled; starts enrichment and navigates to the review page.
