# Newsletter — System Guide

Everything you need to understand, operate, and extend the newsletter pipeline.

---

## Architecture overview

```
newsletter/
├── email-template.html        Master HTML template — {{PLACEHOLDERS}} only
├── COMPONENTS.html            Copy-paste HTML building blocks for TIP_BODY
├── ISSUE-SCHEMA.md            Content spec — field rules, types, quality bar
├── GOD-PROMPT-RUBRIC.md       6-criterion rubric for god-tier prompts
├── build.py                   Assembly script: markdown → rendered HTML
├── send.js                    Broadcast creator: Beehiiv draft or Resend preview
├── render-test.sh             Playwright render at 4 viewports
├── test-god-prompt.py         3-codebase test harness for prompt-architect
├── BACKLOG.md                 Planned issues pipeline with topics + rotation
├── SYSTEM.md                  This file
├── .env                       API keys (never commit)
├── foundations/
│   ├── PERSONA.md             Who you're writing for
│   ├── VOICE.md               How you write
│   ├── GLOSSARY.md            What terms mean in your newsletter
│   ├── BUDGETS.md             Word counts, link limits, visual block limits
│   ├── PIPELINE-PERSONALITIES.md  Email client rendering quirks
│   └── MISTAKES-LOG.md        Every documented failure + its prevention check
└── issues/
    ├── 001.md                 Issue source (human-readable markdown)
    ├── 001-rendered.html      Rendered output (generated by build.py, not edited)
    ├── 002.md
    └── …
```

**What each file does:**

- `email-template.html` — the single source of truth for visual design. Edit once to customise your brand; never edit between issues.
- `COMPONENTS.html` — a library of pre-styled blocks (step, code, callout, result, etc.) to paste into `TIP_BODY`. Use these; don't invent new inline styles.
- `ISSUE-SCHEMA.md` — defines every required field, the quality bar, and the type rotation. Every agent reads this before drafting.
- `build.py` — takes an issue number, parses the markdown, replaces all `{{PLACEHOLDERS}}` in the template, validates, and writes the rendered HTML.
- `send.js` — reads the rendered HTML and either sends a preview via Resend or creates a Beehiiv broadcast draft.
- `BACKLOG.md` — topic pipeline. Fill this before your first issue; don't start from a blank slate.
- `.env` — API keys for Resend and Beehiiv. Required at send time. Never commit this file.

---

## Weekly workflow

From nothing to sent in one command sequence:

```bash
# 1. Pick the next topic and write the brief
/newsletter strategy 001

# 2. Draft the issue body
/newsletter draft 001

# 3. Design and test the god-tier prompt
/newsletter architect 001

# 4. Build the HTML
/newsletter build 001         # runs: python3 newsletter/build.py 001

# 5. Audit the issue
/newsletter audit 001

# 6. Render test at all viewports
/newsletter render-test 001   # runs: bash newsletter/render-test.sh 001

# 7. Send yourself a preview
/newsletter preview 001       # runs: node newsletter/send.js 001 --preview

# 8. Review in email client — fix any rendering issues in issues/NNN.md and rebuild

# 9. Create the Beehiiv broadcast draft
/newsletter send 001          # runs: node newsletter/send.js 001

# 10. Open Beehiiv, final check, schedule send
```

For a quick build-and-preview on an already-written issue:

```bash
python3 newsletter/build.py 001
node newsletter/send.js 001 --preview
```

---

## The `{{VARIABLE}}` system

`email-template.html` contains placeholder tokens in `{{UPPER_CASE}}` format. `build.py` replaces each one with content from the issue markdown file.

| Placeholder | Source in issue markdown |
|---|---|
| `{{ISSUE_NUM}}` | frontmatter `issue:` field |
| `{{DATE}}` | frontmatter `date:` field |
| `{{READ_TIME}}` | computed from word count (200 words/min) |
| `{{DIFFICULTY}}` | frontmatter `difficulty:` field (default: Beginner-friendly) |
| `{{TIP_HEADLINE}}` | `## TIP_HEADLINE` section |
| `{{TLDR}}` | `## TLDR` section |
| `{{TIP_INTRO}}` | `## TIP_INTRO` section |
| `{{TIP_BODY}}` | `## TIP_BODY` section (raw HTML from COMPONENTS) |
| `{{DEEP_DIVE_TITLE}}` | `## DEEP_DIVE_TITLE` section |
| `{{DEEP_DIVE_URL}}` | `## DEEP_DIVE_URL` section |
| `{{DEEP_DIVE_DESCRIPTION}}` | `## DEEP_DIVE_DESCRIPTION` section |
| `{{PROMPT}}` | `## PROMPT` section |
| `{{PRO_TIP_HEADLINE}}` | `## PRO_TIP_HEADLINE` section |
| `{{PRO_TIP_BODY}}` | `## PRO_TIP_BODY` section |
| `{{QUICK_WIN_HEADLINE}}` | `## QUICK_WIN_HEADLINE` section |
| `{{QUICK_WIN_BODY}}` | `## QUICK_WIN_BODY` section |
| `{{READ_THIS_WEEK}}` | `## READ_THIS_WEEK` section |
| `{{GOD_GIFT_BLOCK}}` | rendered from `## GOD_PROMPT_BODY` (empty if not present) |
| `{{NEXT_TEASE}}` | `## NEXT_TEASE` section |
| `{{UNSUBSCRIBE_URL}}` | auto-set to `{{unsubscribe_url}}` (Beehiiv injects this) |

**Rule:** `{{PLACEHOLDERS}}` only appear in `email-template.html`. Issue markdown files contain the content. Never put `{{VARIABLE}}` syntax in issue files — they won't be processed.

---

## How the build pipeline works

`build.py` runs in three stages:

1. **Parse** — reads `issues/NNN.md`, extracts frontmatter key-value pairs and `## SECTION` blocks with regex. Sections are terminated by the next `## UPPER_CASE_NAME` heading or end of file.

2. **Replace** — loads `email-template.html` and does string replacement for each `{{PLACEHOLDER}}` → field value mapping. `{{UNSUBSCRIBE_URL}}` is always replaced with Beehiiv's `{{unsubscribe_url}}` template variable.

3. **Validate** — scans the rendered HTML for any remaining `{{...}}` patterns, checks for placeholder strings in code blocks, verifies required sections are present. Build fails with a clear error list if any check fails.

Output goes to `issues/NNN-rendered.html`. Do not edit rendered files directly — edit the source `NNN.md` and rebuild.

---

## How the send pipeline works

`send.js` reads:
1. `issues/NNN.md` — for frontmatter (`subject`, `preview`)
2. `issues/NNN-rendered.html` — for the HTML body

**Preview mode** (`--preview` flag):
- Sends a real email to `PREVIEW_TO` address via Resend
- Subject is prefixed with `[PREVIEW HHMMSS]` so Gmail doesn't thread and trim repeated previews
- Requires `RESEND_API_KEY` in `.env`

**Broadcast mode** (default):
- POSTs to Beehiiv API: `POST /v2/publications/{pub_id}/broadcasts`
- Creates a draft with `status: "draft"` — not scheduled, not sent
- Prints the broadcast ID and direct Beehiiv draft URL
- Requires `BEEHIIV_API_KEY` and `BEEHIIV_PUB_ID` in `.env`

Review the Beehiiv draft and schedule it manually. `send.js` never publishes directly.

---

## How to write a good issue

### What makes a good tip

A good tip has all four of these:

1. **Concrete action** — a specific flag, config key, file pattern, or command. Not vague advice.
2. **Measurable outcome** — "this saves 20 min/week", "stops Claude asking this question", "your pipeline runs in CI".
3. **Under 10 minutes to apply** — if setup takes longer, split it into two issues.
4. **Insider knowledge** — not on the first page of the docs. Things people discover after months of use.

### What to avoid

- Vague tips: "structure your config well" — not actionable
- Doc summaries: stuff obvious from reading getting-started guides
- Feature announcements without application: "Claude Code now supports X" without showing how to use X
- Long setup chains: if you need 5+ steps, the technique isn't focused enough

### The TIP_BODY structure (always use this sequence)

```
THE SETUP        → 1 paragraph: what problem exists and why it matters
THE TECHNIQUE    → 2-4 numbered steps with code blocks
THE RESULT       → 1 result block: what changes, how much
```

Every code block must be runnable as-is. No `<YOUR_VALUE>` placeholders.

---

## Type rotation and why

Five types rotate to ensure coverage of the full surface area:

| Type | Focus | Example |
|---|---|---|
| Config | Config rules, hooks, settings patterns | Stop hook that updates context |
| Command | A specific flag or command most people haven't tried | `--output-format stream-json` |
| Workflow | Multi-step sequence automating a recurring task | Running Claude Code in CI |
| Agent | How to set up or use a specialist subagent | `.claude/agents/` definition files |
| Debug | Diagnosing and fixing a specific class of problem | Fixing context drift with `/compact` |

The rotation prevents issues from becoming a flag reference (all Command) or a philosophy blog (all Workflow). Each type forces a different kind of research.

---

## Quality gates before sending

Before `send.js` is run, all of the following must be true:

- `build.py` passes with zero unfilled placeholders
- Subject line 50 characters or fewer
- Preview text 110 characters or fewer
- Every code block tested or verified (no placeholder values)
- Quick win is genuinely different from the main tip
- Preview email reviewed in a real email client (`/newsletter preview NNN`)
- Beehiiv draft reviewed at the draft URL before scheduling

The build gate is automated. Everything else is your review before scheduling in Beehiiv.

---

## API credentials

All credentials live in `newsletter/.env`. Never commit this file.

```bash
# newsletter/.env — create this file manually, do not commit
RESEND_API_KEY=re_...            # Resend secret key (for preview sends)
BEEHIIV_API_KEY=...              # Beehiiv API key (for broadcast creation)
BEEHIIV_PUB_ID=pub_...          # Beehiiv publication ID (from your Beehiiv dashboard)
RESEND_FROM=Your Name <hello@yourdomain.com>   # Sender display name + address
PREVIEW_TO=you@yourdomain.com   # Preview recipient (your inbox)
```

`send.js` loads `.env` automatically at startup. No need to export manually.

---

## God-tier prompt architecture

Each issue ships with a god-tier prompt — a paste-ready, production-ready prompt that implements the issue's technique.

The `prompt-architect` agent:
1. Writes a candidate prompt
2. Tests it against 3 reference codebases using `test-god-prompt.py`
3. Scores each output against the 6-criterion rubric in `GOD-PROMPT-RUBRIC.md`
4. Iterates until the gate is cleared: 4+ on at least 5 of 6 criteria
5. Embeds the passing prompt in the issue

The gate is hard. No prompt that fails ships. This is what separates the god-tier prompt from a ChatGPT paste.
