feat(outreach): admin outreach pipeline + separate sending domain (all subtasks)
Adds cold B2B supplier outreach pipeline isolated from transactional emails. Subtask 1 — Migration + constants: - Migration 0024: 4 new columns on suppliers (outreach_status, outreach_notes, last_contacted_at, outreach_sequence_step); NULL status = not in pipeline - EMAIL_ADDRESSES["outreach"] = hello.padelnomics.io (separate reputation domain) - "outreach" added to EMAIL_TYPES Subtask 2 — Query functions + routes: - get_outreach_pipeline() — counts by status for pipeline cards - get_outreach_suppliers() — filtered list with status/country/search - GET /admin/outreach — pipeline dashboard - GET /admin/outreach/results — HTMX partial - POST /admin/outreach/<id>/status — inline status update - POST /admin/outreach/<id>/note — inline note edit - POST /admin/outreach/add-prospects — bulk set from supplier list Subtask 3 — CSV import: - GET/POST /admin/outreach/import - Accepts name+contact_email (required), country_code/category/website (optional) - Deduplicates by contact_email, auto-generates slug, capped at 500 rows Subtask 4 — Templates: - outreach.html (pipeline cards + HTMX filter + results table) - outreach_import.html (CSV upload form) - partials/outreach_results.html, partials/outreach_row.html - base_admin.html: Outreach sidebar link - suppliers.html + supplier_results.html: checkbox column + bulk action bar Subtask 5 — Compose integration: - email_compose() GET: ?from_key=outreach&email_type=outreach&supplier_id=<id> pre-fills from-addr, stores hidden fields, defaults wrap=0 (plain text) - email_compose() POST: on outreach send, advances prospect→contacted, increments outreach_sequence_step, sets last_contacted_at - email_compose.html: hidden email_type + supplier_id fields, outreach banner - supplier_detail.html: outreach card (status, step, last contact, send button) Subtask 6 — Tests: - 44 tests in web/tests/test_outreach.py covering: constants, access control, query functions, dashboard, HTMX partial, status update, note update, add-prospects, CSV import, compose pre-fill, compose pipeline update Subtask 7 — Docs: - CHANGELOG.md and PROJECT.md updated Manual step after deploy: add hello.padelnomics.io in Resend dashboard + DNS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -7,6 +7,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **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
|
||||
|
||||
Reference in New Issue
Block a user