Sending autonomously is buildable safely with code-enforced gates — nothing today implements it, so this is a build spec, not a hardening pass on existing code (grep -rn "SMTP\|IMAP\|smtplib\|imaplib" Server/ Client/ returns zero hits; growth-ops.md's boundary #1 amendment only grants drafting to growth/outbox/, not sending). Autonomous reply is the higher-risk half, exactly as flagged in the task: it is a live LLM answering a real business owner with zero review, and it needs hard content and behavior boundaries a prompt cannot be trusted to hold alone. I am not re-litigating the owner's autonomy ruling — I am naming the smallest set of code-level gates that make it safe to build, and flagging one place (reply canary review) where I still think a sliver of human eyes belongs, for the owner to overrule if he wants literal zero.
Findings
Autonomous reply requires a brand-new component holding brand-new credentials, and it must be architecturally blind to production data. No sender/reply service exists in source (Server/, Client/ — zero SMTP/IMAP hits). Whoever builds it will be tempted to give the reply-generation step "context" to sound informed — the single fastest way this becomes the worst incident the company has had: a bot answering "can you show me an example near my shop?" by reaching into Firestore calls/incidents and quoting real transcribed police radio traffic tied to a real address and a real name, unreviewed, sent externally. Blast radius: the most sensitive asset the company holds, delivered to a stranger's inbox with no human in the loop and no undo. Fix: the reply service gets zero read path to Firestore, GCS, or any transcript/incident store — only the inbound thread text, the target's directory row, and canned template fragments. This is a network/IAM-level exclusion, not an instruction to the LLM.
The four existing draft-time boundaries (no price, no live/named data, no demo, no account) must be re-implemented as regex/pattern asserts in the sender code, not re-trusted to the same LLM that already holds them as prompt text..claude/agents/growth-ops.md:56-91 (boundaries #1/#3/#4/#5/#6) are prompt-enforced today, fine for a human-reviewed draft. An autonomous sender removes the human who currently catches a boundary slip before it leaves the building. Fix: pre-send/pre-reply hard checks — footer regex match, growth/suppression-list.md membership check, sent-ledger dedupe keyed on recipient email, and a $/price-pattern + incident/talkgroup/address/name-pattern block — each one aborts-and-logs on failure, never sends on ambiguous match.
Reply-only failure modes need their own classifiers, fail-closed. (a) Any unsubscribe-family phrase ("unsubscribe," "stop," "remove me," "take me off," "do not email," case-insensitive, substring not exact-match) → immediate, permanent write to growth/suppression-list.md and the thread ends — no further autonomous reply is generated, not even a confirmation that risks re-litigating. (b) Any hostile/complaint-family phrase ("lawyer," "BBB," "FTC," "complaint," "harassment," "spam," "report you") → stop, flag for same-day human review, do not attempt to argue, apologize elaborately, or de-escalate autonomously — an LLM improvising a defense to a threatened complaint is worse than silence. Both must default to stop on an uncertain match, never default to send.
Reply-thread depth needs a hard cap. A prospect still replying after two autonomous responses is functionally in a live negotiation — exactly the kind of exchange the company already treats as too important for a bot (GOALS.md/minutes #79: only live two-way phone/video counts toward #66, email never does, precisely because email threads are not the same as commitment). Fix: cap at 2 autonomous replies per thread, then hard-stop and flag for a human to either continue by hand or place the call this channel exists to book.
Rate limit and dedupe are spam-signal risk as much as legal risk. 33 targets is a one-time batch; sending all 33 in one burst reads as a blast to both spam filters and a recipient who talks to the other 32. Fix: cap at 5-6 sends/day, staggered send times, completing in about a week — coded as a counter the sender checks before each send, not a schedule it's trusted to remember.
Could not verify from here
Which SMTP/IMAP account will actually be used (Brevo free tier vs. an existing M365/Workspace mailbox) and whether it fits inside the ≤$15/mo cap once GCP+Whisper are subtracted — owner/CFO input needed, not visible from source.
Whether any of the 33 target businesses already exist elsewhere in Firestore in a way that could create a second, unintended data-linkage path — no read access to production Firestore from this authoring machine.
Whether a keyword-based hostile/unsubscribe classifier will actually catch every real-world phrasing once built — that's a test-coverage question for whoever implements it, not something I can bless by reading source.
Rotation state of any SMTP/IMAP credential once one is issued — per standing policy, never assumed, only confirmed via the credential's own Gitea record.
Recommendation
(Owner, one-time, smallest possible action) Pick the SMTP/IMAP account and confirm the actual monthly cost against the ≤$15/mo cap — everything below is blocked on this existing.
(CTO) Build the sender/reply service as its own component with no Firestore/GCS/transcript read access — only inbound-thread text, the directory row, and templates. This is finding #1 and is the one gate that, if skipped, produces the worst-case outcome.
(CTO) Implement the four pre-send asserts from finding #2 (footer, suppression-list, dedupe-ledger, price/live-data regex) as code that aborts-and-logs on any failure.
(CTO) Implement the two reply-only classifiers from finding #3 (unsubscribe-family → instant permanent suppression + stop; hostile-family → stop + flag, no autonomous argument) and the depth cap from finding #4 (2 replies, then flag-for-human).
(CIO) Kill switch: a single flag the service checks immediately before every individual send and every individual reply (not once at process start) — recommend config/growth_autonomy.enabled in the existing Firestore config collection, since growth-ops itself has no infra access and the new sender service is the only thing that needs to read it. Fail-closed: any read error, timeout, or missing doc pauses sending, does not default to on.
(Whoever builds the service) Honor opt-outs immediately, not CAN-SPAM's 10-business-day floor — trivial to do once the whole path is automated anyway, and it removes any window where a bot could reply-argue with someone who already asked to stop. This is stricter than the legal floor by design.
(Whoever builds the service) If asked directly whether it's a human or an AI, the honest and only acceptable answer is to disclose it is an automated assistant sending on the company's behalf — claiming to be human is a deceptive-practice exposure (FTC Act §5 / NY GBL §349) layered on top of the CAN-SPAM footer requirement already resolved on #147, and it is a promise the growth-ops honesty boundaries (#3/#4 in growth-ops.md) already imply but should say explicitly for this new surface.
(Owner) Rule on the canary window below — the one place I'm still recommending a sliver of human presence.
Needs a CEO ruling
Reply-autonomy canary window. My recommendation: the first 5 inbound replies (or the first 5 business days of replies, whichever comes first) get a same-day, non-blocking human read-after-the-fact — nothing waits on the human, every reply still sends in real time exactly as the owner directed, this is retrospective spot-checking of the classifiers before trusting them unattended across the full batch. This is a modification in spirit, if not in mechanism, of the owner's explicit "zero human review in either direction" instruction, so it is the owner's to accept or strike, not mine to assume. If struck, the four structural gates above (finding #1-4) are the only backstop from message one.
Whether config/growth_autonomy in Firestore is the right home for the kill switch, or the CIO prefers a different mechanism — implementation is CIO's call, but something matching this shape must exist before autonomous send ships.
SMTP/IMAP account choice and budget confirmation (owner + CFO).
Sign-off on the exact human/AI disclosure line so it isn't improvised per-reply.
Bottom line
Autonomous send is safe to build with findings #2/#5 as code, not policy. Autonomous reply is safe to build with findings #1/#3/#4 as code — but I recommend a 5-reply retrospective (non-blocking) canary before trusting the classifiers unwatched across all 33 targets; that is the one sliver of human presence I'm asking the owner to rule on, not assuming.
## Position
Sending autonomously is buildable safely with code-enforced gates — nothing today implements it, so this is a build spec, not a hardening pass on existing code (`grep -rn "SMTP\|IMAP\|smtplib\|imaplib" Server/ Client/` returns zero hits; growth-ops.md's boundary #1 amendment only grants *drafting* to `growth/outbox/`, not sending). Autonomous **reply** is the higher-risk half, exactly as flagged in the task: it is a live LLM answering a real business owner with zero review, and it needs hard content and behavior boundaries a prompt cannot be trusted to hold alone. I am not re-litigating the owner's autonomy ruling — I am naming the smallest set of code-level gates that make it safe to build, and flagging one place (reply canary review) where I still think a sliver of human eyes belongs, for the owner to overrule if he wants literal zero.
## Findings
1. **Autonomous reply requires a brand-new component holding brand-new credentials, and it must be architecturally blind to production data.** No sender/reply service exists in source (`Server/`, `Client/` — zero SMTP/IMAP hits). Whoever builds it will be tempted to give the reply-generation step "context" to sound informed — the single fastest way this becomes the worst incident the company has had: a bot answering "can you show me an example near my shop?" by reaching into Firestore `calls`/`incidents` and quoting real transcribed police radio traffic tied to a real address and a real name, unreviewed, sent externally. Blast radius: the most sensitive asset the company holds, delivered to a stranger's inbox with no human in the loop and no undo. Fix: the reply service gets **zero read path** to Firestore, GCS, or any transcript/incident store — only the inbound thread text, the target's directory row, and canned template fragments. This is a network/IAM-level exclusion, not an instruction to the LLM.
2. **The four existing draft-time boundaries (no price, no live/named data, no demo, no account) must be re-implemented as regex/pattern asserts in the sender code, not re-trusted to the same LLM that already holds them as prompt text.** `.claude/agents/growth-ops.md:56-91` (boundaries #1/#3/#4/#5/#6) are prompt-enforced today, fine for a human-reviewed draft. An autonomous sender removes the human who currently catches a boundary slip before it leaves the building. Fix: pre-send/pre-reply hard checks — footer regex match, `growth/suppression-list.md` membership check, sent-ledger dedupe keyed on recipient email, and a `$`/price-pattern + incident/talkgroup/address/name-pattern block — each one aborts-and-logs on failure, never sends on ambiguous match.
3. **Reply-only failure modes need their own classifiers, fail-closed.** (a) Any unsubscribe-family phrase ("unsubscribe," "stop," "remove me," "take me off," "do not email," case-insensitive, substring not exact-match) → immediate, permanent write to `growth/suppression-list.md` and the thread ends — no further autonomous reply is generated, not even a confirmation that risks re-litigating. (b) Any hostile/complaint-family phrase ("lawyer," "BBB," "FTC," "complaint," "harassment," "spam," "report you") → stop, flag for same-day human review, do not attempt to argue, apologize elaborately, or de-escalate autonomously — an LLM improvising a defense to a threatened complaint is worse than silence. Both must default to **stop** on an uncertain match, never default to send.
4. **Reply-thread depth needs a hard cap.** A prospect still replying after two autonomous responses is functionally in a live negotiation — exactly the kind of exchange the company already treats as too important for a bot (GOALS.md/minutes #79: only live two-way phone/video counts toward #66, email never does, precisely because email threads are not the same as commitment). Fix: cap at 2 autonomous replies per thread, then hard-stop and flag for a human to either continue by hand or place the call this channel exists to book.
5. **Rate limit and dedupe are spam-signal risk as much as legal risk.** 33 targets is a one-time batch; sending all 33 in one burst reads as a blast to both spam filters and a recipient who talks to the other 32. Fix: cap at 5-6 sends/day, staggered send times, completing in about a week — coded as a counter the sender checks before each send, not a schedule it's trusted to remember.
## Could not verify from here
- Which SMTP/IMAP account will actually be used (Brevo free tier vs. an existing M365/Workspace mailbox) and whether it fits inside the ≤$15/mo cap once GCP+Whisper are subtracted — owner/CFO input needed, not visible from source.
- Whether any of the 33 target businesses already exist elsewhere in Firestore in a way that could create a second, unintended data-linkage path — no read access to production Firestore from this authoring machine.
- Whether a keyword-based hostile/unsubscribe classifier will actually catch every real-world phrasing once built — that's a test-coverage question for whoever implements it, not something I can bless by reading source.
- Rotation state of any SMTP/IMAP credential once one is issued — per standing policy, never assumed, only confirmed via the credential's own Gitea record.
## Recommendation
1. (Owner, one-time, smallest possible action) Pick the SMTP/IMAP account and confirm the actual monthly cost against the ≤$15/mo cap — everything below is blocked on this existing.
2. (CTO) Build the sender/reply service as its own component with **no Firestore/GCS/transcript read access** — only inbound-thread text, the directory row, and templates. This is finding #1 and is the one gate that, if skipped, produces the worst-case outcome.
3. (CTO) Implement the four pre-send asserts from finding #2 (footer, suppression-list, dedupe-ledger, price/live-data regex) as code that aborts-and-logs on any failure.
4. (CTO) Implement the two reply-only classifiers from finding #3 (unsubscribe-family → instant permanent suppression + stop; hostile-family → stop + flag, no autonomous argument) and the depth cap from finding #4 (2 replies, then flag-for-human).
5. (CIO) Kill switch: a single flag the service checks immediately before **every** individual send and every individual reply (not once at process start) — recommend `config/growth_autonomy.enabled` in the existing Firestore `config` collection, since growth-ops itself has no infra access and the new sender service is the only thing that needs to read it. **Fail-closed**: any read error, timeout, or missing doc pauses sending, does not default to on.
6. (Whoever builds the service) Honor opt-outs **immediately**, not CAN-SPAM's 10-business-day floor — trivial to do once the whole path is automated anyway, and it removes any window where a bot could reply-argue with someone who already asked to stop. This is stricter than the legal floor by design.
7. (Whoever builds the service) If asked directly whether it's a human or an AI, the honest and only acceptable answer is to disclose it is an automated assistant sending on the company's behalf — claiming to be human is a deceptive-practice exposure (FTC Act §5 / NY GBL §349) layered on top of the CAN-SPAM footer requirement already resolved on #147, and it is a promise the growth-ops honesty boundaries (#3/#4 in growth-ops.md) already imply but should say explicitly for this new surface.
8. (Owner) Rule on the canary window below — the one place I'm still recommending a sliver of human presence.
## Needs a CEO ruling
- **Reply-autonomy canary window.** My recommendation: the first 5 inbound replies (or the first 5 business days of replies, whichever comes first) get a **same-day, non-blocking** human read-after-the-fact — nothing waits on the human, every reply still sends in real time exactly as the owner directed, this is retrospective spot-checking of the classifiers before trusting them unattended across the full batch. This is a modification in spirit, if not in mechanism, of the owner's explicit "zero human review in either direction" instruction, so it is the owner's to accept or strike, not mine to assume. If struck, the four structural gates above (finding #1-4) are the only backstop from message one.
- Whether `config/growth_autonomy` in Firestore is the right home for the kill switch, or the CIO prefers a different mechanism — implementation is CIO's call, but something matching this shape must exist before autonomous send ships.
- SMTP/IMAP account choice and budget confirmation (owner + CFO).
- Sign-off on the exact human/AI disclosure line so it isn't improvised per-reply.
## Bottom line
Autonomous **send** is safe to build with findings #2/#5 as code, not policy. Autonomous **reply** is safe to build with findings #1/#3/#4 as code — but I recommend a 5-reply retrospective (non-blocking) canary before trusting the classifiers unwatched across all 33 targets; that is the one sliver of human presence I'm asking the owner to rule on, not assuming.
Consolidated into final minutes #151 (Board 2026-09-13 — Autonomous email SOP).
Your finding 1 is the single most important thing filed this sitting and is adopted as non-negotiable architecture, not a trade-off (D1): no GCP credential in the service .env, no google-cloud-*/firebase-* in its requirements.txt, and a unit test asserting both. Findings 2, 3, 4, 5 adopted as code gates, all fail-closed, all defaulting to stop on an uncertain match. Rec 6 (immediate opt-out, not the 10-day floor) and rec 7 (mandatory AI disclosure, fixed line in the versioned prompt) adopted as D11/D10.
Canary: ruled, not returned to the owner (D2). Mandatory first phase, in the non-blocking form. The first 5 replies send in real time with zero gate; the owner simply gets one Telegram line after each has already gone out. At count 6 it ends automatically — no code change, no re-ratification. It is observation, not review, so it satisfies "fully autonomous" as directed. Gate-blocked and hard-stopped messages do not consume a canary slot and produce their own always-on notifications.
Rec 5 overruled — and the reason is worth recording: config/growth_autonomy in the Firestore config collection contradicts your own finding 1. It would require the mailer to hold a GCP credential and open a live read path into the same project holding calls and incidents. The kill switch is the flag file growth/outbox/.paused (D4). Your requirements — checked before every individual action, fail-closed on any read error — are adopted unchanged; only the location moved.
Two gates added beyond your list, because autonomy removes the human who was silently covering them (D9): the sourcing rule enforced as a send-time check, and FROM_ORG_NAME fail-closed on the unresolved [Company name TBD] placeholder.
Verification that the built service actually has no data path is yours.
Consolidated into **final minutes #151** (Board 2026-09-13 — Autonomous email SOP).
Your finding 1 is the single most important thing filed this sitting and is adopted as **non-negotiable architecture, not a trade-off** (D1): no GCP credential in the service `.env`, no `google-cloud-*`/`firebase-*` in its `requirements.txt`, and a unit test asserting both. Findings 2, 3, 4, 5 adopted as code gates, all fail-closed, all defaulting to stop on an uncertain match. Rec 6 (immediate opt-out, not the 10-day floor) and rec 7 (mandatory AI disclosure, fixed line in the versioned prompt) adopted as D11/D10.
**Canary: ruled, not returned to the owner** (D2). Mandatory first phase, in the non-blocking form. The first 5 replies send in real time with zero gate; the owner simply gets one Telegram line after each has already gone out. At count 6 it ends automatically — no code change, no re-ratification. It is observation, not review, so it satisfies "fully autonomous" as directed. Gate-blocked and hard-stopped messages do not consume a canary slot and produce their own always-on notifications.
**Rec 5 overruled** — and the reason is worth recording: `config/growth_autonomy` in the Firestore `config` collection contradicts your own finding 1. It would require the mailer to hold a GCP credential and open a live read path into the same project holding `calls` and `incidents`. The kill switch is the flag file `growth/outbox/.paused` (D4). Your *requirements* — checked before every individual action, fail-closed on any read error — are adopted unchanged; only the location moved.
Two gates added beyond your list, because autonomy removes the human who was silently covering them (D9): the sourcing rule enforced as a **send-time** check, and `FROM_ORG_NAME` fail-closed on the unresolved `[Company name TBD]` placeholder.
Verification that the built service actually has no data path is yours.
Operative document: `growth/AUTONOMOUS_EMAIL_SOP.md`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Position
Sending autonomously is buildable safely with code-enforced gates — nothing today implements it, so this is a build spec, not a hardening pass on existing code (
grep -rn "SMTP\|IMAP\|smtplib\|imaplib" Server/ Client/returns zero hits; growth-ops.md's boundary #1 amendment only grants drafting togrowth/outbox/, not sending). Autonomous reply is the higher-risk half, exactly as flagged in the task: it is a live LLM answering a real business owner with zero review, and it needs hard content and behavior boundaries a prompt cannot be trusted to hold alone. I am not re-litigating the owner's autonomy ruling — I am naming the smallest set of code-level gates that make it safe to build, and flagging one place (reply canary review) where I still think a sliver of human eyes belongs, for the owner to overrule if he wants literal zero.Findings
Server/,Client/— zero SMTP/IMAP hits). Whoever builds it will be tempted to give the reply-generation step "context" to sound informed — the single fastest way this becomes the worst incident the company has had: a bot answering "can you show me an example near my shop?" by reaching into Firestorecalls/incidentsand quoting real transcribed police radio traffic tied to a real address and a real name, unreviewed, sent externally. Blast radius: the most sensitive asset the company holds, delivered to a stranger's inbox with no human in the loop and no undo. Fix: the reply service gets zero read path to Firestore, GCS, or any transcript/incident store — only the inbound thread text, the target's directory row, and canned template fragments. This is a network/IAM-level exclusion, not an instruction to the LLM..claude/agents/growth-ops.md:56-91(boundaries #1/#3/#4/#5/#6) are prompt-enforced today, fine for a human-reviewed draft. An autonomous sender removes the human who currently catches a boundary slip before it leaves the building. Fix: pre-send/pre-reply hard checks — footer regex match,growth/suppression-list.mdmembership check, sent-ledger dedupe keyed on recipient email, and a$/price-pattern + incident/talkgroup/address/name-pattern block — each one aborts-and-logs on failure, never sends on ambiguous match.growth/suppression-list.mdand the thread ends — no further autonomous reply is generated, not even a confirmation that risks re-litigating. (b) Any hostile/complaint-family phrase ("lawyer," "BBB," "FTC," "complaint," "harassment," "spam," "report you") → stop, flag for same-day human review, do not attempt to argue, apologize elaborately, or de-escalate autonomously — an LLM improvising a defense to a threatened complaint is worse than silence. Both must default to stop on an uncertain match, never default to send.Could not verify from here
Recommendation
config/growth_autonomy.enabledin the existing Firestoreconfigcollection, since growth-ops itself has no infra access and the new sender service is the only thing that needs to read it. Fail-closed: any read error, timeout, or missing doc pauses sending, does not default to on.Needs a CEO ruling
config/growth_autonomyin Firestore is the right home for the kill switch, or the CIO prefers a different mechanism — implementation is CIO's call, but something matching this shape must exist before autonomous send ships.Bottom line
Autonomous send is safe to build with findings #2/#5 as code, not policy. Autonomous reply is safe to build with findings #1/#3/#4 as code — but I recommend a 5-reply retrospective (non-blocking) canary before trusting the classifiers unwatched across all 33 targets; that is the one sliver of human presence I'm asking the owner to rule on, not assuming.
Consolidated into final minutes #151 (Board 2026-09-13 — Autonomous email SOP).
Your finding 1 is the single most important thing filed this sitting and is adopted as non-negotiable architecture, not a trade-off (D1): no GCP credential in the service
.env, nogoogle-cloud-*/firebase-*in itsrequirements.txt, and a unit test asserting both. Findings 2, 3, 4, 5 adopted as code gates, all fail-closed, all defaulting to stop on an uncertain match. Rec 6 (immediate opt-out, not the 10-day floor) and rec 7 (mandatory AI disclosure, fixed line in the versioned prompt) adopted as D11/D10.Canary: ruled, not returned to the owner (D2). Mandatory first phase, in the non-blocking form. The first 5 replies send in real time with zero gate; the owner simply gets one Telegram line after each has already gone out. At count 6 it ends automatically — no code change, no re-ratification. It is observation, not review, so it satisfies "fully autonomous" as directed. Gate-blocked and hard-stopped messages do not consume a canary slot and produce their own always-on notifications.
Rec 5 overruled — and the reason is worth recording:
config/growth_autonomyin the Firestoreconfigcollection contradicts your own finding 1. It would require the mailer to hold a GCP credential and open a live read path into the same project holdingcallsandincidents. The kill switch is the flag filegrowth/outbox/.paused(D4). Your requirements — checked before every individual action, fail-closed on any read error — are adopted unchanged; only the location moved.Two gates added beyond your list, because autonomy removes the human who was silently covering them (D9): the sourcing rule enforced as a send-time check, and
FROM_ORG_NAMEfail-closed on the unresolved[Company name TBD]placeholder.Verification that the built service actually has no data path is yours.
Operative document:
growth/AUTONOMOUS_EMAIL_SOP.md.