# Session Handoff

**Date:** 2026-04-08
**Session:** Full scaffold + v0.0.1 → v0.0.3 implementation of `mailing-list-cli`, plus a parity plan to Beehiiv / MailChimp
**Context usage at handoff:** ~85%

---

## Active Plan

**Plan file:** [`docs/plans/2026-04-08-parity-plan.md`](../plans/2026-04-08-parity-plan.md)

This plan maps the remaining work from `v0.0.3` → `v0.1.5` across 7 phases. Each phase produces a shippable release tag. Phases 3 → 6 cover the user's five explicit asks (bounce rate, unsubscribe count per batch, named/segmented campaigns, click tracking, templates with agent authoring guidelines).

**Plan status:** all phases unstarted except Phase 1 (shipped as v0.0.1) and Phase 2 (shipped as v0.0.2 + migrated in v0.0.3). Next phase: **Phase 3 — Contacts, tags, fields, segments (v0.0.4)**.

### Supporting documents

- **Design spec:** [`docs/specs/2026-04-07-mailing-list-cli-design.md`](../specs/2026-04-07-mailing-list-cli-design.md) — 1,206 lines. The authoritative reference for architecture, data model, command surface, send pipeline, compliance invariants, and the embedded template authoring guide.
- **Phase 1 implementation plan** (as a template for future phases): [`docs/plans/2026-04-07-phase-1-foundations.md`](../plans/2026-04-07-phase-1-foundations.md) — 1,897 lines, fully executed.
- **Gap analysis vs email-cli** (marked **STATUS: FULFILLED**): [`docs/email-cli-gap-analysis.md`](../email-cli-gap-analysis.md)
- **Research dossiers:** [`/research`](../../research/) — five competitor + platform deep dives generated by parallel agents on 2026-04-07.

## What Was Accomplished This Session

**GitHub repo creation + polish (2026-04-07):**
- Created `paperfoot/mailing-list-cli` as a public repo
- Full README with hero card (`assets/og-card.png` — 1536×1024 dev-tool aesthetic, generated via `chatgpt-image` skill)
- `LICENSE` (MIT), `AGENTS.md`, `CONTRIBUTING.md`
- 20 SEO topics configured, Discussions enabled, pinned roadmap issue #1

**Research swarm (2026-04-07):**
- Dispatched 5 parallel general-purpose agents via `superpowers:dispatching-parallel-agents`
- Each wrote a focused dossier to `/research`:
  1. Modern creator newsletters (Beehiiv, Buttondown, Substack)
  2. Marketing platforms (MailChimp, MailerLite, Kit)
  3. Resend native capabilities (exact API endpoints)
  4. Deliverability + compliance at scale (CAN-SPAM, GDPR, Gmail/Yahoo 2024)
  5. Email template formats (recommended MJML via `mrml` + Handlebars)

**Architecture pivot:**
- User corrected architectural direction mid-session: `mailing-list-cli` must shell out to `email-cli`, not call Resend directly
- Applied pivot across README, spec, AGENTS.md, research dossiers, roadmap issue

**Design spec:**
- Wrote `docs/specs/2026-04-07-mailing-list-cli-design.md` (1,206 lines) covering architecture, 16-table SQLite schema, full command surface (~70 commands across 11 nouns), send pipeline with non-skippable compliance boundary, filter expression grammar, MJML template system, suppression list semantics, GDPR erasure flow, webhook listener, config, error model, exact email-cli interface (now §13), roadmap, and embedded template authoring guide

**v0.0.1 — Foundations (shipped and tagged):**
- Cargo scaffold (Rust 2024, MSRV 1.85)
- `src/paths.rs` (XDG paths + env overrides)
- `src/error.rs` (semantic exit codes 0/1/2/3/4, structured `AppError`)
- `src/output.rs` (JSON envelope + TTY auto-detection)
- `src/config.rs` (TOML loader with defaults)
- `src/db/` (16-table schema, migration runner)
- `src/email_cli.rs` (subprocess wrapper for email-cli)
- `src/cli.rs` (clap definitions)
- `src/commands/{agent_info,health,update,skill}.rs`
- `.github/workflows/ci.yml` (fmt + clippy + build + test)
- 13 unit + 7 integration tests, all green
- Release binary: 2.5 MB, sub-10ms cold start

**v0.0.2 — Lists & Contacts (shipped and tagged):**
- `src/models.rs` (typed `List` and `Contact` serialization structs)
- `src/db/mod.rs` extended with `list_create/list_all/list_get_by_id/list_get_by_name/contact_upsert/contact_add_to_list/contact_list_in_list`
- `src/commands/list.rs` (`create/ls/show` with local pre-check before email-cli call)
- `src/commands/contact.rs` (`add/ls` with email validation, list existence check, three-step create flow)
- Extended stub `email-cli` script to mock audience + contact commands
- 18 unit + 12 integration tests, all green

**Gap analysis committed (2026-04-07 → 2026-04-08):**
- First long version at `docs/email-cli-gap-analysis.md`
- User asked to tighten it — rewrote as **three focused asks** (`contact --properties`, `email list`, `broadcast` noun), plus a "what NOT to build" section
- The email-cli team shipped **all three asks** within ~12 hours in `email-cli v0.6.2`, plus the surprise retirement of the deprecated `audience` noun

**v0.0.3 — Migrate to email-cli v0.6 (shipped and tagged):**
- Rewrote `src/email_cli.rs`: `audience_create → segment_create`; `contact_create` signature now takes `(email, first_name, last_name, segments, properties)` and uses the flat `/contacts` endpoint with `--segments` and `--properties` flags
- Renamed DB column `list.resend_audience_id → list.resend_segment_id` (clean break — no compat shim, pre-release)
- Updated `src/models.rs`, `src/db/mod.rs`, `src/commands/list.rs`, `src/commands/contact.rs`, `tests/cli.rs`, `tests/fixtures/stub-email-cli.sh`
- Marked `docs/email-cli-gap-analysis.md` as **STATUS: FULFILLED** at the top
- Rewrote design spec §13 (The email-cli Interface) for the new command surface
- Bumped `Cargo.toml` to `0.0.3`; agent-info manifest now declares `depends_on: ["email-cli >= 0.6.0"]`
- 30 tests green, clippy clean, fmt clean, CI passing

**Parity plan to Beehiiv/MailChimp (this session, 2026-04-08):**
- Wrote `docs/plans/2026-04-08-parity-plan.md` (~400 lines) mapping the 80/20 feature set from the research dossiers to concrete phases v0.0.4 → v0.1.5
- Includes a feature matrix comparing mailing-list-cli to Beehiiv / MailChimp / MailerLite / Kit, acceptance criteria per phase, and scope-discipline rules

## Key Decisions Made

**Architectural:**

1. **`mailing-list-cli` has zero Resend HTTP code.** Every Resend touchpoint flows through `email-cli`. This was a user correction mid-session and it shapes every subsequent design decision. A mailing-list-cli "list" backs onto a Resend segment. A mailing-list-cli "broadcast" will call `email-cli broadcast create --send`. Custom fields round-trip via `email-cli contact create --properties`.

2. **Local SQLite is the source of truth for everything Resend doesn't expose.** That includes templates, campaigns, suppression (Resend has no suppression API), double-opt-in tokens, segment definitions, engagement mirrors. Resend is the transport + the contacts mirror only.

3. **MJML + Handlebars for templates, compiled in-process via `mrml`.** Explicitly NOT React Email / jsx-email / Maizzle — those all require Node. Research dossier 05 walked through the tradeoffs and `mrml` (pure Rust reimplementation of MJML) is the winner. Merge tags are Mustache-style `{{ snake_case }}` because LLMs author that most reliably.

4. **The user's "batch" = mailing-list-cli's "broadcast".** When the user said "segment batches and give them names" they meant named campaigns targeting a filter. Model it as `broadcast create --name "Q4" --template x --to segment:engaged`.

5. **Webhooks vs polling:** the preferred path post-email-cli-v0.6 is polling `email-cli email list --after <id>` for delivery status. The local webhook listener stays as a fallback (and for full engagement events). This means mailing-list-cli MAY be able to ship Phase 6 without running a webhook listener at all, depending on how far the `last_event` field gets us. Decide during Phase 6 implementation.

**Implementation gotchas encountered this session:**

6. **Rust 2024 edition made `std::env::set_var` unsafe.** Tests that mutate env vars for `MLC_CONFIG_PATH` / `MLC_DB_PATH` need to wrap the calls in `unsafe { ... }` blocks. Already handled in `src/paths.rs` tests.

7. **rusqlite `params!` macro type unification bites on heterogeneous args.** `[version, &now]` where `version: &&str` and `now: String` fails with "expected `&&str`, found `&String`". Workaround: `[*version, now.as_str()]`. Already handled in `src/db/mod.rs::run_migrations`.

8. **`ALTER TABLE RENAME COLUMN` needs SQLite 3.25+**, which `rusqlite 0.37` with `bundled` satisfies. Not relevant in the current code because the v0.0.3 migration is pre-release (clean break), but matters if we ever add post-v0.1.0 migrations.

9. **The `.claude/tsc-cache/` directory from the Claude Code harness gets auto-staged.** Added to `.gitignore` in the first commit but worth remembering: if you see weird staged files after a `git status`, check for harness artifacts.

10. **email-cli's CLI grammar is clap-based with `#[arg(long, visible_alias = "ls")]`.** When reading the source to audit what commands exist, look for the `#[derive(Subcommand)]` enums and the `#[derive(Args)]` structs. The top-level enum is in `src/cli.rs`.

**Tooling gotchas:**

11. **Codex CLI hung twice this session.** Once because of a stale process from a prior session (killed PIDs 2313/2316/2319), once because `codex exec` with a long multiline prompt via argv waits on stdin for additional input instead of running. The fix (for next time) is to pipe the prompt via stdin: `echo "prompt" | codex exec ...` instead of passing it as an argument.

12. **`chatgpt-image` skill default style is Bauhaus-Swiss.** For dev-tool aesthetics (dark navy, monospace, minimal icons), you have to explicitly override the default style in the prompt: "NO Bauhaus, NO illustrations of people. Flat vector, technical, minimal type only." The og-card.png result came out great on the first try with that override.

13. **GitHub social preview cannot be uploaded via `gh` CLI.** The PNG is in `/assets/og-card.png` and embedded in the README, but setting it as the actual GitHub OG card requires a manual click in Settings → Options → Social preview. Not yet done.

**Scope discipline:**

14. **Automations (drip sequences) are deferred** until Resend's Automations API exits private alpha. Resend has a native DAG automation engine (`POST /automations` with conditional edges, wait_for_event, contact_update, segment_add actions) — but it's Node.js SDK preview only. When it GAs, mailing-list-cli's automation surface becomes a thin `email-cli automation` wrapper instead of a custom DAG engine. That's a massive scope reduction for Phase 10+.

15. **Tags, custom fields, segments all have database schemas already in place** from migration 0001. Phase 3 is "add the CLI surface to wire them" — no schema work, just commands + a filter expression parser.

## Current State

- **Branch:** `main`
- **Last commit:** `b2d8206 feat: ship v0.0.3 — migrate to email-cli v0.6 (audiences → segments)`
- **Tags:** `v0.0.1`, `v0.0.2`, `v0.0.3` all pushed
- **Uncommitted changes:** yes — the new `docs/plans/2026-04-08-parity-plan.md` and this handoff file (not yet committed)
- **Tests passing:** yes (30 tests: 18 unit + 12 integration)
- **Build status:** clean (debug and release)
- **clippy:** clean with `-D warnings`
- **cargo fmt:** clean

## What to Do Next

1. **Read this handoff document** and the parity plan at `docs/plans/2026-04-08-parity-plan.md`.
2. **Commit this handoff and the parity plan** if not already committed:
   ```bash
   cd /Users/biobook/Projects/mailing-list-cli
   git add docs/plans/2026-04-08-parity-plan.md docs/handoffs/2026-04-08-session-handoff.md
   git commit -m "docs: parity plan + session handoff"
   git push
   ```
3. **Start Phase 3 (v0.0.4 — Contacts, tags, fields, segments)** by writing its detailed implementation plan. Follow the same format as `docs/plans/2026-04-07-phase-1-foundations.md`:
   - Save to `docs/plans/2026-04-08-phase-3-contacts-tags-fields-segments.md`
   - Each task is one action (2–5 minutes): write failing test → run it → write minimal code → run again → commit
   - Bite-sized tasks. TDD. Frequent commits.
   - Use the `superpowers:writing-plans` skill to generate it.
4. **Execute Phase 3.** Either inline with the `superpowers:executing-plans` skill, or with parallel subagents via `superpowers:subagent-driven-development` when tasks are independent (e.g., `tag` commands and `field` commands don't share files beyond `src/cli.rs`).
5. **Tag v0.0.4** when Phase 3 is complete and tests are green.
6. **Proceed to Phase 4 (templates)**, then Phase 5 (broadcasts), Phase 6 (webhooks + reports) — at which point all five of the user's original asks are shipped.

### Order-of-operations note for Phase 3

The filter expression parser (using the `pest` crate) is the highest-risk item in Phase 3. Build it first, even before the `contact tag` command, because:
- Segments depend on it
- `contact ls --filter` depends on it
- Getting the grammar right requires iteration — it's best done before the commands that consume it

Then build in this order: `tag` commands → `field` commands → `contact tag/untag/set/show/erase/import` → `segment create/ls/show/members`. That order has strictly-increasing dependencies.

## Files to Review First

1. **[`docs/specs/2026-04-07-mailing-list-cli-design.md`](../specs/2026-04-07-mailing-list-cli-design.md)** — the authoritative design. Read §3 (data model), §4.1–4.4 (lists/contacts/tags/fields/segments command surface), §6 (filter expression language), §13 (email-cli interface). Skim everything else.
2. **[`docs/plans/2026-04-08-parity-plan.md`](../plans/2026-04-08-parity-plan.md)** — the strategic roadmap with the feature matrix and user-ask mapping.
3. **[`docs/plans/2026-04-07-phase-1-foundations.md`](../plans/2026-04-07-phase-1-foundations.md)** — the template for future per-phase implementation plans. Same format applies to Phase 3.
4. **[`src/db/mod.rs`](../../src/db/mod.rs)** — already has `list_*` and `contact_*` helpers. Phase 3 adds `tag_*`, `field_*`, `segment_*` methods in the same style.
5. **[`src/cli.rs`](../../src/cli.rs)** — where new subcommands land. Follow the existing pattern: top-level `Command` enum, per-noun `enum XAction`, per-action `struct XArgs`.

## Gotchas & Warnings

- **Don't try to make `mailing-list-cli` talk to Resend directly.** That was an explicit user correction on day 1 and the whole architecture is built around shelling out to `email-cli`. If you find yourself reaching for `reqwest`, stop and shell out instead.
- **Don't reinvent a DAG automation engine.** Resend's Automations API is in private alpha. When it GAs, we wrap it via an `email-cli automation` noun. Do not build a custom engine in Phase 3–9.
- **Don't add features that aren't in the parity plan's §5 phase list without updating the plan first.** The scope discipline rules (§6) exist to prevent Phase 3 from spiraling into a mini-MailChimp clone.
- **The test stub email-cli at `tests/fixtures/stub-email-cli.sh` has to be updated any time `mailing-list-cli` adds a new `email-cli` subcommand invocation.** The stub currently mocks: agent-info, profile test, segment create, contact create/get/update/delete, email list, broadcast create/send/list. Add new cases as needed.
- **`cargo fmt` will automatically rewrap long match statements and `.ok_or_else` chains** in the `src/email_cli.rs` file. Not a bug, but expect the diff to look different from what you wrote.
- **Every email-cli command mailing-list-cli calls needs a test assertion against the stub's canned response** — if the stub returns `{"id":"seg_test_12345"}` but you expect `{"data":{"id":"..."}}`, the test will silently pass while real email-cli fails. When in doubt, inspect the real email-cli output format first.
- **The `v0.0.1 Foundations` release badge in README has NOT been updated to `v0.0.3`** as of commit `b2d8206` — I updated the status section text but the badge URL still says v0.0.1 Foundations in the Phase 3 plan. Check and update if needed.
  - **Correction:** verified, the badge *was* updated to `v0.0.3_email--cli_v0.6` in the v0.0.3 commit. This warning is obsolete — included here only because I mentioned the badge mid-session and want the next session to sanity check.
- **CI on the v0.0.3 commit was "in_progress" when this handoff was written.** Double-check it went green with `gh run list --repo paperfoot/mailing-list-cli --limit 1` before starting Phase 3 work.
- **The `chatgpt-image` skill and `github-optimization` skill are already active in this session and should still be available.** No need to re-activate.

---

**Session entry point for the next run:**

> Read `docs/handoffs/2026-04-08-session-handoff.md`, then `docs/plans/2026-04-08-parity-plan.md`. Start Phase 3 by writing its bite-sized implementation plan to `docs/plans/2026-04-08-phase-3-contacts-tags-fields-segments.md` using the `superpowers:writing-plans` skill, then execute it.
