Newsletter engine

Project
0
by · 7 items · 4 scripts · 1 flow · 2 resources github resend

Write the newsletter from the recent changelog and leave it as a draft in Resend

Flow
Triggers (1) disabled on import
  • schedule Reads the changelog entries published since the last issue, writes the newsletter, and leaves it as a draft in Resend for a human to read and send. NOTHING IS EVER SENT. The flow creates a broadcast and stops. There is no send call anywhere in it, disabled or otherwise, so a schedule that fires unattended cannot mail anyone. The worst case of a bad run is a draft nobody opens. MONTHLY, ON PURPOSE. A newsletter earns attention by being worth opening, and a month is roughly how long it takes to accumulate two or three things that are. Running it weekly would mean padding, and the writer would have to skip most weeks anyway - which it will do, with a reason, if the month is thin. TUNING since_days. 35 covers a month with slack at both edges, so a run on the 1st still catches something published late on the 30th. Raise it if you send less often than monthly; the limit parameter caps the read either way. THE EDITORIAL REPO IS THE BRAIN. voice, what earns a section, what gets one line and past issues all live there as markdown, and fetch_editorial reads them into the writer's system prompt. When a draft reads wrong, fix the skill that produced it rather than the scripts - that is the whole point of the shape. Create the repo with init_newsletter_repo. WHAT IS NOT IN THE REPO is the email's structure: it is fixed by the flow's output schema and rendered by code. There is no format file to keep in sync, and the writer and the renderer cannot disagree. Configuration lives in the args below, and the scripts carry no defaults tied to any organisation - not the changelog repo, not the site URL, not the company name, not the logo. docs_repo and site_base are required and refuse to run empty: a newsletter silently built from somebody else's changelog, or carrying their branding, is a worse failure than one that stops. This copy ships neutral, with dry_run on.
    Cron
    0 0 9 1 * *
    Timezone
    Europe/Paris

README

Newsletter engine

Turns the recent changelog into a written newsletter and leaves it as a draft in Resend. A human reads it and presses send.

The pipeline

Step
fetch_changelognativeCurated changelog entries published in the window
fetch_editorialnativeVoice, editorial rules and the last two issues, from a GitHub repo
writeAI agent stepThe issue, as JSON — structure enforced by output_schema
create_resend_draftnativeRenders the branded HTML, creates the draft

Monthly schedule, shipped disabled.

Two things worth knowing

It cannot send. Resend sends on POST /broadcasts/{id}/send, and no step here builds that URL — not behind a flag, not disabled. The worst case of a bad run is a draft nobody opens.

The repo is the brain. How the newsletter reads lives in a GitHub repo as markdown, not in these scripts. Tuning it is a reviewed pull request rather than a deploy.

newsletter/
├── skills/voice.md        how it sounds
├── skills/editorial.md    what earns a section, what gets one line
└── issues/2026-08.md      past issues — the writer reads the last two

There is no format file: the email's structure is fixed by the flow's output_schema and rendered by code, so the writer and the renderer cannot disagree.

Setup

  1. Run init_newsletter_repo with dry_run: false — creates the repo, skills included.
  2. On the flow, set repo, ai_resource (a $res: path, not a resource picker), segment_id, from_address, plus the gh_auth and resend resources.
  3. Run it with dry_run: false. A draft appears in Resend.

dry_run (the default) renders the whole email from samples — no GitHub, no model call, no Resend.

When a draft reads wrong

Fix the skill that produced it, not the scripts.

  • Sounds like marketing → voice.md
  • Covered something trivial, or missed the big one → editorial.md
  • Nothing worth sending this month → that is what skip is for, and a padded issue is worse

Drop each issue you send into issues/YYYY-MM.md. Past issues show the judgement calls instead of describing them, and they beat any rule file.