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:
| 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) |
| Nodemailer over SMTP (MailHog in dev) | |
| Template engine | GrapesJS (visual), MJML (code), Handlebars (merge fields) |
| Infrastructure | Docker Compose (all services) |
| 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 |
URL: /login
What you see:
you@example.com)How to use:
Forgot password: Click Forgot password?, enter your email, then Send reset link. Open the link in the message (it opens /reset-password), set a new password, and you are signed in to the Dashboard. Add the exact redirect URL (e.g. http://localhost:8098/reset-password) to Supabase Auth redirect allow-list; the app uses the current browser origin for redirectTo.
URL: / (root, after login)
What you see:
Sidebar (left): Persistent navigation with links to all sections:
Main area:
How to use:
URL: /campaigns
What you see:
How to use:
URL: /campaigns/:id
What you see:
How to use:
URL: /contacts
What you see:
How to use:
URL: /lists
What you see:
How to use:
URL: /templates
What you see:
How to use:
{{first_name}})
URL: /analytics
Before selecting a campaign: filter field, campaign dropdown, message “Select a sent campaign to view analytics”.
After selecting a campaign: Sent, Unique Opens, Unique Clicks, Bounces; Events Over Time chart; legend (Opens, Clicks, Sent).
How to use:
URL: /transactional
What you see: “Transactional Emails” heading, Send Email button, history rows with subject, recipient, time, status (sent / queued).
How to use:
URL: /settings
What you see: Save Settings; SMTP Host, Port, Username, Password; dev note about MailHog; Default From Name and Email.
How to use:
http://localhost:8025
URL: /users
What you see (non-admin): shield icon, “Admin access required”, and explanatory text.
Admin features: list users, invite users, change roles, delete users.
How to use: Restricted to the admin role; regular users must contact an administrator.
| 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 | AI tone, instructions, send-from, reply-unsubscribe, forward/BCC |
campaign_enriched_emails | Original vs enriched copy, review status, Message-ID |
inbox_messages | Inbound reply log and classification |
| 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 LLM enrichment job |
| GET | /api/campaigns/:id/enriched | List enriched rows |
| 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 |
| PUT | /api/campaigns/:id/enriched/:emailId/reject | Reject one |
| POST | /api/campaigns/:id/send-enriched | Send approved enriched |
| GET | /api/campaigns/:id/inbox | Inbound messages |
A transparent 1×1 pixel image is injected into each campaign email. When the recipient’s email client loads the image, an “opened” event is recorded.
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.
Standard sends: include a link to /track/unsubscribe/:token. Enriched + reply mode: when enabled, sends omit List-Unsubscribe; recipients reply to opt out. Inbound messages are classified (keywords, then LLM), the contact is unsubscribed, and an auto-reply is sent.
OpenAI rewrites each recipient’s subject/body from the merged template. Operators approve or reject in /campaigns/:id/enrichment, then Send approved. Configure tone, instructions, send-from, and forward/BCC on the campaign detail card or at campaign creation.
Contacts may store enrichment_data (JSON) for extra context (role, company, Apollo exports).
With MS365_IMAP_* set, the API polls the inbox, processes unseen mail, unsubscribes or forwards per campaign_settings, and logs rows in inbox_messages.
Slimmer GrapesJS panels, refined toolbar, collapsible merge-field hints, and an Enrich & review action when AI is enabled.
High-level relationships and the two new pipelines (enrichment and inbound reply handling). Diagrams render in modern browsers via Mermaid.
Generated from Scope_and_User_Guide.md. Open this file from the docs/ folder so screenshot paths resolve correctly. Section 11 diagrams use Mermaid loaded from a CDN.