merge(outreach): admin outreach pipeline + separate sending domain

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Deeman
2026-02-25 15:29:20 +01:00
15 changed files with 1325 additions and 7 deletions

View File

@@ -15,6 +15,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- **`analytics.execute_user_query()`** — new function returning `(columns, rows, error, elapsed_ms)` for admin query editor
- **`worker.run_extraction` task** — background handler shells out to `uv run extract` from repo root (2h timeout)
- 29 new tests covering all routes, data access helpers, security checks, and `execute_user_query()`
- **Outreach pipeline** — cold B2B supplier outreach isolated from transactional emails:
- **Separate sending domain** (`hello.padelnomics.io`) — added `"outreach"` key to `EMAIL_ADDRESSES`; reputation isolated from `notifications.padelnomics.io` magic-link/lead-forward traffic (manual DNS step: add domain in Resend dashboard)
- **Migration 0024** — 4 new columns on `suppliers`: `outreach_status`, `outreach_notes`, `last_contacted_at`, `outreach_sequence_step`; `NULL` status = not in pipeline (no backfill needed for existing suppliers)
- **Admin outreach pipeline tab** (`/admin/outreach`) — 6 pipeline cards (prospect → contacted → replied → signed_up → declined → not_interested) with click-to-filter; HTMX-powered supplier table with inline status dropdown + note editing; sidebar link added
- **HTMX endpoints** — `POST /admin/outreach/<id>/status` returns updated row; `POST /admin/outreach/<id>/note` returns truncated note text
- **Bulk add-to-pipeline** — checkbox column on `/admin/suppliers`, "Add to Outreach Pipeline" form action → `POST /admin/outreach/add-prospects`; skips suppliers already in pipeline
- **CSV import** (`GET/POST /admin/outreach/import`) — uploads CSV (`name`, `contact_email` required; `country_code`, `category`, `website` optional); creates new supplier rows as `prospect`; auto-generates slug; deduplicates by `contact_email`; capped at 500 rows
- **Compose integration** — `GET /admin/emails/compose` now accepts `?from_key=outreach&email_type=outreach&supplier_id=<id>` query params; pre-selects outreach from-address and unchecks HTML wrap (plain text best practice for cold email); on successful send with `email_type=outreach` + `supplier_id`, auto-updates supplier: `prospect→contacted`, `last_contacted_at=now`, `outreach_sequence_step+1`
- **Supplier detail outreach card** — shown when supplier is in the outreach pipeline; displays status, step, last contact date, notes, and "Send Outreach Email" compose link
- 44 new tests in `web/tests/test_outreach.py`
- **Email template system** — all 11 transactional emails migrated from inline f-string HTML in `worker.py` to Jinja2 templates:
- **Standalone renderer** (`email_templates.py`) — `render_email_template()` uses a module-level `jinja2.Environment` with `autoescape=True`, works outside Quart request context (worker process); `tformat` filter mirrors the one in `app.py`
- **`_base.html`** — branded shell (dark header, 3px blue accent, white card body, footer with tagline + copyright); replaces the old `_email_wrap()` helper