5.5 KiB
Campaigns
Email campaign management — create campaigns, pick an email page template, target contact groups, and track delivery.
Architecture Overview
┌──────────────────────────────────────┐
│ Frontend │
│ CampaignsManager.tsx │
│ (MUI DataGrid, PagePicker, GroupPicker) │
│ │
│ client-campaigns.ts │
│ (fetch wrappers, bearer token) │
└──────────────┬───────────────────────┘
│ /api/campaigns/*
▼
┌──────────────────────────────────────┐
│ Server – CampaignsProduct │
│ products/campaigns/index.ts │
│ products/campaigns/routes.ts │
└──────────────┬───────────────────────┘
│ Supabase
▼
┌──────────────────────────────────────┐
│ Tables │
│ campaigns │
│ (+ marketing_emails for sends) │
└──────────────────────────────────────┘
Database
campaigns
| Column | Type | Notes |
|---|---|---|
id |
uuid | PK, auto-generated |
owner_id |
uuid | FK → auth.users, not null |
name |
text | Campaign label, not null |
page_slug |
text | Page slug to render as email |
page_id |
text | Optional page UUID |
subject |
text | Email subject line |
group_ids |
uuid[] | Target contact group IDs |
lang |
text | Language tag (en, de, …) |
tracking_id |
text | Tracking param injected into URLs |
vars |
jsonb | Template variables (--var-* equivalent) |
status |
text | draft / scheduled / sending / sent / failed |
stats |
jsonb | { total, sent, failed, skipped } |
scheduled_at |
timestamptz | When to send (null = manual) |
started_at |
timestamptz | When sending began |
completed_at |
timestamptz | When sending finished |
meta |
jsonb | Arbitrary metadata |
created_at |
timestamptz | — |
updated_at |
timestamptz | Auto-updated via trigger |
Indexes: owner_id, status, page_slug
RLS
- Owners: full CRUD on their own rows (
owner_id = auth.uid()) - Admins (
user_roles.role = 'admin'): full access to all rows
Server API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/campaigns |
Auth | List campaigns. Query: ?status=&q=&limit=&offset= |
POST |
/api/campaigns |
Auth | Create campaign |
GET |
/api/campaigns/:id |
Auth | Get single campaign |
PATCH |
/api/campaigns/:id |
Auth | Update campaign (partial) |
DELETE |
/api/campaigns/:id |
Auth | Delete campaign |
Frontend Client
src/modules/campaigns/client-campaigns.ts — all functions inject Supabase bearer token automatically.
fetchCampaigns(options?) → Campaign[] // options: { status?, q?, limit?, offset? }
getCampaign(id) → Campaign
createCampaign(data) → Campaign
updateCampaign(id, data) → Campaign
deleteCampaign(id) → void
Frontend UI — CampaignsManager
Mounted at /user/:id/campaigns via UserProfile.tsx.
| Feature | Detail |
|---|---|
| DataGrid | Columns: name, page, status, groups, stats, created_at, actions |
| URL state sync | Filter, sort, visibility, pagination persisted in search params |
| Toolbar | Search, status filter, "New Campaign" button |
| Campaign dialog | Name, subject, language, tracking ID, PagePickerDialog for email page, ContactsPicker for groups |
| Status workflow | draft → scheduled → sending → sent / failed |
Relationship to Email System
The campaigns table is the parent that defines what to send and to whom. The existing marketing_emails table continues to track individual sends per recipient. A future "Send Campaign" action will resolve group members, render the email page, and use the email tracking flow (POST /api/email/track → send → PATCH /api/email/track/:id) — the same pipeline currently used by the CLI email-send command.
Environment Variables
Inherits same Supabase env as the rest of the server — no additional variables required.
Source Files
| File | Description |
|---|---|
| campaigns.md | This document |
| migration | DB schema, RLS, indexes |
| routes.ts | Zod-OpenAPI route definitions |
| index.ts | CampaignsProduct handlers |
| client-campaigns.ts | Frontend fetch wrappers |
| CampaignsManager.tsx | Main UI component (DataGrid, dialogs) |
| ContactsPicker.tsx | Group selection component |
| PagePickerDialog.tsx | Page selection dialog |