Skip to content
🎓 Find your path Subscribe

The Newsletter Agent

Tier 3 · What we built 8 min read

Before this, read:


The Agent Brief is a weekly newsletter for the AgentTree Army list — real subscribers, real sends via Resend, real deliverability requirements. It was one of the first pieces of the growth infrastructure to get built and, for four weeks, barely shipped anything. The audit that found this is worth as much as the system that fixed it.

After two rounds of root-cause work (2026-06-05 audit, 2026-06-06 v2 rebuild, 2026-06-06 Phase 2 grounding), the pipeline runs as:

  1. Daily AI-news sweep (6pm MT, agents/growth/ai_news_sweep.py)

    Pulls from Brave News API + Socrates X fetcher. Deduplicates. Runs a grounding-guarded LLM pass to extract what’s significant. Writes a dated digest to ~/clawd/state/ai-news/YYYY-MM-DD.md with real source URLs.

  2. Thursday draft (Thursday 7pm MT, newsletter-thursday-draft.py + scripts/cron-newsletter-thursday-draft.sh)

    Reads the last 7 days of AI-news digests. Picks ONE item per section with its real source URL. Generates the draft issue in the newsletter_issues Supabase table (status=draft).

  3. Telegram ping (same cron)

    Pings JD with a preview of the draft. Reachable as /nl-status in the command palette.

  4. One-tap approve (newsletter_approval_gate.py, triggered by JD /nl-yes)

    Marks the issue approved → fans out to the subscriber list via newsletter.py send. Real sends to 11 active subscribers (as of 2026-06-06).

Before the v2 rebuild, the CHANGELOG entry at 2026-06-06 07:22 recorded what the audit found:

“14 ranked issues. Headline — barely ships (4 of 26 issues sent, last 5/22), NO send automation (the ‘Friday send’ cron is a commentless stub).”

Four sends in four weeks from a weekly newsletter. The root-cause list:

  1. Broken unsubscribe link (404, CAN-SPAM violation) — every email had a dead unsubscribe URL.
  2. Missing List-Unsubscribe header — required by Gmail and Outlook for bulk senders.
  3. Image-heavy template — 8 PNG images hosted on raw Supabase storage URLs, which spam filters flag.
  4. SPF From-misalignment — the From address was on the root domain; sending happened from a subdomain not covered by the SPF record.
  5. The “Friday send” cron was a stub — it existed in the crontab as a comment, never actually called the send function.
  6. 22 rotting draft issues in the database from months of Thursday draft runs that never got approved or sent.

Items 1–4 explain spam landing. Item 5 explains the zero automation. Item 6 explains the 22 orphaned drafts.

The one-pass fix addressed every item on the audit list:

  • Template rebuilt to zero images (live text only, light/dark, mobile responsive). ~22KB down to ~10KB.
  • Unsubscribe link fixed — now routes to docs.agenttree.army/api/unsubscribe?email= with a real handler.
  • List-Unsubscribe one-click header added.
  • newsletter_approval_gate.py restored (it had been lost in a codebase cleanup — found by checking the RUNBOOK, which referenced it).
  • Thursday draft cron fixed — the stub was replaced with a real call.
  • 22 draft orphans pruned. 16 test subscribers audited down to 11 real ones + 5 test addresses removed.
  • Disabled the broken Wednesday cron that was generating duplicate drafts.

The test-send verification: draft UUID a2421abe sent to JD, confirmed delivered (likely landed in Promotions/Spam — the reputation signal takes a few issues to build, which is expected for a new sending domain).

The v2 rebuild fixed the sending problems. The content had a different problem: the AI-news section was generated from model memory — the LLM writing “recent AI news” from training data, not from actual recent sources. An agent brief with “AI news” that is 6–12 months stale is worse than no AI-news section.

Phase 2 (committed 2026-06-06, 3dcc714) added the daily AI-news sweep as the grounding layer. The Thursday draft now reads last week’s digests and picks ONE item with its real source_url instead of asking the model to recall what happened in AI. Verified end-to-end: the 2026-06-06 digest produced 10 real linked items; the Thursday dry-run used one of those links verbatim.

This project is the canonical example of CLAUDE.md’s “RUNBOOK first, draft second” rule. When JD asked for a newsletter issue on 2026-06-06, the first action was: check ~/clawd/projects/agent-tree-growth/newsletter/ for the pipeline. It existed. The issue was drafted by running:

Terminal window
bash ~/agent-system/scripts/newsletter-friday-draft.py
# → sections JSON → newsletter.py draft → send.py --to JD

Not by hand-writing a new format. The working pipeline and the proper Supabase-backed flow were already there; the issue was that nobody had run the audit to find out it was broken.

If the RUNBOOK had been read at the start of every session that touched the newsletter, the spam and stub-cron problems would have been caught weeks earlier. The lesson added to CLAUDE.md: “If a project has no RUNBOOK and you can’t find tooling, create the RUNBOOK as part of finishing the work.” The newsletter project now has one.

  • 11 active subscribers
  • Weekly cadence: Thursday draft → Friday send (one-tap approve)
  • Two sections: BUILD (what shipped this week) + AI NEWS (one grounded item with source URL)
  • Open/click tracking: deferred to a future phase (no webhook yet)
  • Archive pages: deferred (no static archive route yet)

The pipeline is small and honest. It produces one real email per week with one real AI-news item, a description of what shipped, and a working unsubscribe link. That’s the baseline that works. Sophistication can be added on top.


This concludes Group F. For the agents that manage JD’s life domains — school, health, family, finance — see Group G starting with the expert professor agent.