Outreach Wave 3 — Send-Queue Audit Notes (2026-07-25)
This note records the Wave 3 outreach send queue as it stands on 2026-07-25. The queue itself is persisted in the database (see migrations/010_create_outreach_wave3_queue.js and the inspection endpoint GET /api/admin/outreach/queue). The draft copy and the founder-pain source material both live outside this repository — in the agent harness / copy library. This article is the in-repo audit trail only.
It does not change any application behavior in this repository. It is a pointer, not duplication.
Acceptance contract — referenced, not duplicated
From the Wave 3 brief:
> Acceptance: queue is persisted as a structured send-ready artifact (per-recipient record: prospect, archetype, subject, body, status=queued-not-sent); founder-pain library and 4-archetype library are referenced, not duplicated; ready to fire in one operation the moment POL-1492 comes back, with zero additional drafting.
The contract has two halves:
1. The 16 per-recipient records (4 prospects × 4 archetypes) ARE persisted here — in outreach_wave3_queue. Subject lines, body copy, and HTML versions are rendered at seed time (migration 010). 2. The 4-archetype library and the founder-pain library are NOT persisted here. They are referenced by id and library_ref only. A future maintainer who needs to evolve the copy should edit the external library, then re-render this seed if they want the new wording to land in the next batch's queued records.
Wave 3 prospects (4)
| Handle | Display name | Company | Source platform | |---|---|---|---| | spencer-claydon-foundra | Spencer Claydon | Foundra.ai | Hacker News | | mehphp | mehphp | mehphp | IndieHackers | | alexandru-dodon | Alexandru Dodon | dodon.dev | Hashnode | | shahed-khalidi | Shahed Khalidi | khalidi.io | Medium |
Each prospect row's last_post_hook column points at the canonical founder-pain library id — a record in an external library, not a re-embedded quote. The personalization token is substituted into each of the 4 archetype drafts so the seeded subject / body fields show the live name and the live hook reference text. No {{tokens}} are left in the rows.
Wave 3 archetypes (4, pointer-only)
| Archetype id | Display label | Library reference (external) | |---|---|---| | speed_pressure | speed_pressure | outreach-copy-library@/archetypes/speed_pressure.md | | build_vs_buy | build_vs_buy | outreach-copy-library@/archetypes/build_vs_buy.md | | spec_clarity | spec_clarity | outreach-copy-library@/archetypes/spec_clarity.md | | client_validation | client_validation | outreach-copy-library@/archetypes/client_validation.md |
The outreach_wave3_archetypes table in this repo carries only the link (library_ref) and the label. Full archetype copy lives in the external library. If the AC is ever violated by adding a description or body column here, that violates the "referenced, not duplicated" contract — revert it.
Fire trigger
When the Polsia email proxy (https://polsia.com/api/proxy/email/send) recovers from POL-1492, the operator fires the queued batch in one admin call:
POST /api/admin/outreach/send-wave3
Authorization: Bearer <admin-jwt>
The route:
- POSTs each of the 16 records to the proxy using the same
{to, subject, body, html?}shape used by the welcome email inroutes/subscribe.js:48-77. - Marks each row
status='sent'(withproxy_status) on 2xx, orstatus='failed'(withproxy_status+proxy_error) otherwise. - Returns 502 if all attempts came back with 5xx (proxy still down), so the operator sees the real signal.
- Returns 503 with
POLSIA_API_KEY not configuredif the bearer-secret env var is unset — never silently skips.
Re-running the same call after a drain returns total: 0 because the status='queued-not-sent' filter prevents re-firing. Inspect progress with GET /api/admin/outreach/fire-status.
Why the queue is persisted, not deferred
The send queue is persisted in the DB so that:
- The 16 drafts survive a redeploy / sandbox restart — no in-memory state to lose.
- A future operator can inspect, audit, and re-stage by editing rows, without re-running any AI drafting step.
- The fire endpoint stays short — it reads, sends, marks — no rendering logic in the request path.