# Changelog

## [Unreleased]

### Added

- **PRD gap closure:** API rate limiting (`express-rate-limit`), structured HTTP logging (`pino` / `pino-http`), RLS on app tables (`003` / `004` migrations), campaign status **`failed`**, send finalization (partial success vs all failed), SMTP retries for transient errors, RFC 8058 **`List-Unsubscribe-Post`** + `POST /track/unsubscribe/:token`, startup recovery for stuck `sending` campaigns, **scheduled campaign poller** (1 min), optional **Cloudflare Turnstile** on login/sign-up (`VITE_TURNSTILE_SITE_KEY`), transactional **`template_id`**, template **preview** on list + builder, **test send** on campaign detail, dashboard **quick links**, contact **subscription toggle**, analytics **unsubscribe** metrics.
- **Postman:** `postman/Email_Campaigner_API.postman_collection.json` — import for local API testing (`baseUrl`, `accessToken` variables).
- **Migration:** `supabase/migrations/003_profiles.sql` — `profiles` table (`id`, `email`, `role`, `updated_at`) for API auth and user roles. Optional `FOREIGN KEY (id) REFERENCES auth.users(id)` can be added after GoTrue has created `auth.users` (see file comment).
- **Forgot password:** Login screen “Forgot password?” sends Supabase <code>resetPasswordForEmail</code>; public <code>/reset-password</code> page sets a new password after the email link (recovery session). Configure GoTrue/Supabase redirect allow-list for your app origin + <code>/reset-password</code>.
- **AI enrichment pipeline:** Per-recipient OpenAI (GPT-4o) rewrites of campaign HTML/subject; batch approve/reject; send approved via optional Microsoft 365 SMTP (`MS365_SMTP_*`).
- **Campaign settings:** `campaign_settings` table and API (`GET/PUT /api/campaigns/:id/settings`) for tone, instructions, send-from, reply-based unsubscribe, auto-forward/BCC lists.
- **Enrichment review UI:** `/campaigns/:id/enrichment` with side-by-side original vs enriched previews, bulk approve/reject, `POST /api/campaigns/:id/send-enriched`.
- **Contacts:** `enrichment_data` JSON column for extra LLM context; editable on contact detail and optional JSON on add-contact dialog.
- **Mailbox poller:** IMAP (`imapflow`) + `mailparser` to classify replies (keyword + LLM), auto-reply unsubscribe messages, forward other replies, log `inbox_messages`.
- **Migration:** `supabase/migrations/002_enrichment_schema.sql` — `campaign_settings`, `campaign_enriched_emails`, `inbox_messages`, `contacts.enrichment_data`.

### Changed

- **Campaign builder:** Load `GET /api/templates/:id` when `campaign.template_id` is set — previously `initialJson` was `undefined`, so the canvas always showed the default MJML after save/reload.
- **Template / campaign save:** Send `grapesjs_json: data.json ?? {}` and `compiled_html: data.html ?? ''` so `JSON.stringify` does not omit keys and wipe DB columns. On Save, always call `getLatestContent()` first; the debounced `onChange` ref could be stale so reload showed old content.
- **Email builder (MJML):** Compile with GrapesJS `mjml-code-to-html` (plugin MJML parser) instead of POST `/api/templates/compile` with `mjml-code` output — `mjml-code` is canvas HTML, not MJML, which caused mjml2html nesting validation errors server-side.
- **API security:** `requireAuth` on `/api/campaigns` (including enrichment), `/api/contacts`, `/api/lists`, `/api/templates`, `/api/transactional`, `/api/analytics`, `/api/settings` (tracking pixels remain public). Enrichment router is mounted on the same `/api/campaigns` stack so `settings`, `enrich`, `enriched`, `send-enriched`, and `inbox` routes resolve.
- **Docker:** Postgres published on host port **54350** instead of 5435 to avoid blocked Windows port ranges.
- **Campaign builder:** Slimmer GrapesJS panels (CSS), refined toolbar, collapsible merge-field strip; “Enrich & review” when AI is enabled.
- **Campaign detail:** Settings card for AI/mailbox; standard “Send” hidden when enrichment is enabled (use review → send enriched).
