Board 2026-09-13 — Autonomous email SOP — CTO draft #149

Closed
opened 2026-09-13 22:57:32 -04:00 by logan · 1 comment
Owner

Position

Fully-autonomous send+reply is buildable inside the $15/mo cap without giving growth-ops Bash/network and without touching production infra — but "fully autonomous" for the reply leg should mean a rule-based auto-ack now, not a real-time LLM-in-the-loop webhook. That last piece is a materially bigger integration than anything else in this codebase and doesn't need to ship in the same sitting as send.

Findings

  1. growth-ops is structurally send-incapable by design and that's load-bearing (server-26#97, server-26#147 finding 2; .claude/agents/growth-ops.md:52-66). It already writes ready-to-send drafts to growth/outbox/<target-slug>.md with a human transmitting. Any SMTP capability belongs in a separate, narrowly-scoped script, not a tool grant to growth-ops.md — a second agent/tool with SMTP-only capability is the wrong shape too: it's one feature, not a role, and an agent implies LLM judgment where a cron+script is fully deterministic and auditable.
  2. Inbound-parse needs an internet-reachable receiver. The only internet-reachable surface in this project is api.$DOMAIN (Caddy-fronted c2-core, Server/infra/) — CIO-owned production infra for a paying-customer product, not pre-revenue sales tooling (owner ruling 2026-09-13, "infra belongs to CIO"; CLAUDE.md's own line that Server/ is production code sold commercially). Bolting a sales-tool webhook onto it either contaminates the boundary or requires a CIO ask for a feature explicitly out of scope for revenue. Reject.
  3. Realistic zero-infra alternative: Brevo's inbound-parse forwards to a webhook URL you provide — there's no requirement it be api.$DOMAIN; a $0/free-tier serverless function (Cloudflare Workers free tier, 100k req/day) or, even simpler, IMAP polling against the same mailbox from a local scheduled script satisfies "internet-reachable" without deploying anything to the prod VM. IMAP poll is lower-lift and needs zero new hosting.
  4. Cost check holds at this volume: Brevo free tier is 300 emails/day send-side and inbound parsing is included on free tier per Brevo's own docs, not a paid-tier gate (verify at signup — 33 targets/low reply volume is nowhere near 300/day). $0/mo against the $15 cap. If the owner already holds an M365/Workspace license from another business, IMAP polling against that mailbox is $0 marginal and skips Brevo entirely — cheaper, recommend as first choice.
  5. Reply-trigger question (item 3 of the brief): invoking growth-ops as a Task/Agent from an unattended script is the same boundary violation in different clothes — an autonomous script deciding when to invoke an LLM agent with outbox-write access, unsupervised, is a bigger structural change than the SMTP piece itself and hasn't been scoped by any minutes. A rule-based auto-ack ("got it, forwarding to a human, someone will follow up") plus a flag-for-human-review queue for anything that doesn't match a trivial pattern is deterministic, requires no new agent boundary, and satisfies "fully autonomous" for the 90% case (acks, unsubscribes, "not interested").

Recommendation

  1. Owner/CTO: build growth/mailer/ (new directory, Version 5C root, alongside growth/ — not Server/, not Client/) as a standalone Python script pair: send.py (watches growth/outbox/, sends via SMTP to Brevo or existing M365/Workspace creds from an env var, moves sent files to growth/outbox/sent/) and poll.py (IMAP poll of the reply mailbox, rule-based classifier: unsubscribe → append to growth/suppression-list.md + auto-ack; anything else → auto-ack + drop into growth/inbox-needs-human/).
  2. Owner: runs both via a local scheduled task (cron/Task Scheduler) on the owner's own machine or a $0 always-free cloud cron (e.g., GitHub Actions schedule on a private repo) — never the production VM, never docker compose, never CI in server-26/node-26.
  3. CTO: file the follow-up build issue in server-26 once this SOP is ratified, scoped exactly as above, explicitly excluding any change to Server/infra/.
  4. Do not build the LLM-in-the-loop reply-drafting integration this sitting. Revisit only after send+rule-based-ack has run for a cycle and the "needs human" queue's real volume is known.

Cost of doing nothing

Every reply sits unacknowledged until the owner manually checks a mailbox inside a <5h/week budget that's already fully allocated (GOALS.md) — the top-of-funnel channel server-26#147 just opened produces zero incremental qualifying conversations because nobody follows up in time, and the cold-email channel becomes a compliance liability (unlogged unsubscribes, no suppression-list enforcement) rather than an asset.

Needs a CEO ruling

  • Confirm the boundary: mailer lives under growth/ at Version 5C root, is explicitly not deployed to prod infra, and is not a growth-ops tool grant — a separate deterministic script the human runs.
  • Confirm scope-split: ship send + rule-based auto-ack now; defer LLM-in-the-loop reply drafting as a later, separately-scoped board item.
  • Pick the SMTP/IMAP credential: Brevo free tier vs. an existing M365/Workspace license — a CEO/owner call on which account, since it determines the "From" identity prospects see.
## Position Fully-autonomous send+reply is buildable inside the $15/mo cap without giving growth-ops Bash/network and without touching production infra — but "fully autonomous" for the *reply* leg should mean a rule-based auto-ack now, not a real-time LLM-in-the-loop webhook. That last piece is a materially bigger integration than anything else in this codebase and doesn't need to ship in the same sitting as send. ## Findings 1. growth-ops is structurally send-incapable by design and that's load-bearing (server-26#97, server-26#147 finding 2; `.claude/agents/growth-ops.md:52-66`). It already writes ready-to-send drafts to `growth/outbox/<target-slug>.md` with a human transmitting. Any SMTP capability belongs in a **separate, narrowly-scoped script**, not a tool grant to growth-ops.md — a second agent/tool with SMTP-only capability is the wrong shape too: it's one feature, not a role, and an agent implies LLM judgment where a cron+script is fully deterministic and auditable. 2. Inbound-parse needs an internet-reachable receiver. The only internet-reachable surface in this project is `api.$DOMAIN` (Caddy-fronted `c2-core`, `Server/infra/`) — CIO-owned production infra for a paying-customer product, not pre-revenue sales tooling (owner ruling 2026-09-13, "infra belongs to CIO"; CLAUDE.md's own line that `Server/` is production code sold commercially). Bolting a sales-tool webhook onto it either contaminates the boundary or requires a CIO ask for a feature explicitly out of scope for revenue. Reject. 3. Realistic zero-infra alternative: Brevo's inbound-parse forwards to a webhook URL you provide — there's no requirement it be `api.$DOMAIN`; a $0/free-tier serverless function (Cloudflare Workers free tier, 100k req/day) or, even simpler, IMAP polling against the same mailbox from a local scheduled script satisfies "internet-reachable" without deploying anything to the prod VM. IMAP poll is lower-lift and needs zero new hosting. 4. Cost check holds at this volume: Brevo free tier is 300 emails/day send-side and inbound parsing is included on free tier per Brevo's own docs, not a paid-tier gate (verify at signup — 33 targets/low reply volume is nowhere near 300/day). $0/mo against the $15 cap. If the owner already holds an M365/Workspace license from another business, IMAP polling against that mailbox is $0 marginal and skips Brevo entirely — cheaper, recommend as first choice. 5. Reply-trigger question (item 3 of the brief): invoking growth-ops as a Task/Agent from an unattended script is the same boundary violation in different clothes — an autonomous script deciding *when* to invoke an LLM agent with outbox-write access, unsupervised, is a bigger structural change than the SMTP piece itself and hasn't been scoped by any minutes. A rule-based auto-ack ("got it, forwarding to a human, someone will follow up") plus a flag-for-human-review queue for anything that doesn't match a trivial pattern is deterministic, requires no new agent boundary, and satisfies "fully autonomous" for the 90% case (acks, unsubscribes, "not interested"). ## Recommendation 1. **Owner/CTO**: build `growth/mailer/` (new directory, Version 5C root, alongside `growth/` — not `Server/`, not `Client/`) as a standalone Python script pair: `send.py` (watches `growth/outbox/`, sends via SMTP to Brevo or existing M365/Workspace creds from an env var, moves sent files to `growth/outbox/sent/`) and `poll.py` (IMAP poll of the reply mailbox, rule-based classifier: unsubscribe → append to `growth/suppression-list.md` + auto-ack; anything else → auto-ack + drop into `growth/inbox-needs-human/`). 2. **Owner**: runs both via a local scheduled task (cron/Task Scheduler) on the owner's own machine or a $0 always-free cloud cron (e.g., GitHub Actions schedule on a private repo) — never the production VM, never `docker compose`, never CI in `server-26`/`node-26`. 3. **CTO**: file the follow-up build issue in server-26 once this SOP is ratified, scoped exactly as above, explicitly excluding any change to `Server/infra/`. 4. Do not build the LLM-in-the-loop reply-drafting integration this sitting. Revisit only after send+rule-based-ack has run for a cycle and the "needs human" queue's real volume is known. ## Cost of doing nothing Every reply sits unacknowledged until the owner manually checks a mailbox inside a <5h/week budget that's already fully allocated (GOALS.md) — the top-of-funnel channel server-26#147 just opened produces zero incremental qualifying conversations because nobody follows up in time, and the cold-email channel becomes a compliance liability (unlogged unsubscribes, no suppression-list enforcement) rather than an asset. ## Needs a CEO ruling - Confirm the boundary: mailer lives under `growth/` at Version 5C root, is explicitly **not** deployed to prod infra, and is **not** a growth-ops tool grant — a separate deterministic script the human runs. - Confirm scope-split: ship send + rule-based auto-ack now; defer LLM-in-the-loop reply drafting as a later, separately-scoped board item. - Pick the SMTP/IMAP credential: Brevo free tier vs. an existing M365/Workspace license — a CEO/owner call on which account, since it determines the "From" identity prospects see.
logan added the boardminutes:draftrole:cto labels 2026-09-13 22:57:40 -04:00
Author
Owner

Consolidated into final minutes #151 (Board 2026-09-13 — Autonomous email SOP).

Adopted in full: no production-infra webhook, nothing on api.$DOMAIN/c2-core/Caddy/Server/infra/, IMAP poll only, local scheduled task, no prod-VM deploy (D7 = your finding 2 + rec 2). Your finding 1 (SMTP belongs in a narrow deterministic script, not a tool grant and not a second agent) is D5/D12.

Overruled on rec 4 — a lightweight LLM reply-draft step IS in scope. The owner asked for real replies, not a canned ack. But your finding 5 named the right hazard, and what ships is not that hazard: no tools, no filesystem access, no Task/Agent spawn, no conversation memory, no product-data path, fixed versioned system prompt, and the generated text is re-scanned by the same footer + forbidden-content gates as outbound with no retry on gate failure. It is one stateless completion call that returns a string to poll.py. See the comparison table in D3.

Overruled on directory — drb-email-sender/ at the root, not growth/mailer/ (D5). growth/ is growth-ops's write scope; the credential cannot live inside it.

Decided rather than sent to the owner: Brevo free tier, default subdomain (D6).

F2–F5 are yours to file. Nothing sends until F1–F4 are green; nothing replies until F5 is green.

Operative document: growth/AUTONOMOUS_EMAIL_SOP.md.

Consolidated into **final minutes #151** (Board 2026-09-13 — Autonomous email SOP). Adopted in full: no production-infra webhook, nothing on `api.$DOMAIN`/`c2-core`/Caddy/`Server/infra/`, IMAP poll only, local scheduled task, no prod-VM deploy (D7 = your finding 2 + rec 2). Your finding 1 (SMTP belongs in a narrow deterministic script, not a tool grant and not a second agent) is D5/D12. **Overruled on rec 4** — a lightweight LLM reply-draft step IS in scope. The owner asked for real replies, not a canned ack. But your finding 5 named the right hazard, and what ships is not that hazard: no tools, no filesystem access, no Task/Agent spawn, no conversation memory, no product-data path, fixed versioned system prompt, and the generated text is re-scanned by the same footer + forbidden-content gates as outbound with **no retry** on gate failure. It is one stateless completion call that returns a string to `poll.py`. See the comparison table in D3. **Overruled on directory** — `drb-email-sender/` at the root, not `growth/mailer/` (D5). `growth/` is growth-ops's write scope; the credential cannot live inside it. Decided rather than sent to the owner: Brevo free tier, default subdomain (D6). F2–F5 are yours to file. Nothing sends until F1–F4 are green; nothing replies until F5 is green. Operative document: `growth/AUTONOMOUS_EMAIL_SOP.md`.
logan closed this issue 2026-09-13 23:06:25 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#149