Product Changelog
Eine wiederkehrende E-Mail, die Nutzer über Produktänderungen informiert, in natürlicher Sprache verfasst, sicher versendet und anschließend für Social-Media-Beiträge weiterverwendet wird. Die E-Mail ist die maßgebliche Quelle für jede Ausgabe; alle anderen Kanäle werden daraus abgeleitet.
Wie dieses Skill abläuft
- Einmal fragen, zu Beginn. Beim ersten Durchlauf (oder wenn die Konfiguration fehlt oder ein benötigter Wert leer ist) werden die Einrichtungsfragen in einer kurzen Batch-Abfrage gestellt.
config.yamlwird geschrieben und dann fortgefahren, ohne weitere Fragen. - Dann alles erledigen. Im
autonomy.mode: autonomous(Standard) wählst du die Elemente aus, erstellst Entwürfe, prüfst die Formatierung, machst eine Selbstüberprüfung, erstellst Screenshots, versendest Tests, liest den Test zurück, baust die Zielgruppe auf, führst einen Probelauf durch, sendest an alle, überprüfst, holst Opt-Outs ein, schreibst und postest Social-Media-Beiträge und archivierst. Kein Genehmigungsschritt. Jede ausgehende Aktion durchläuft zunächst eine Selbstprüfung. - Geld ist die einzige Genehmigung. Alles, was Geld kostet, wartet in jedem Modus auf das explizite „Ja“ des Benutzers im Chat: ein Versand, der das im Provider-Plan enthaltene Kontingent überschreiten würde (
provider.monthly_quota), ein Upgrade des Provider-Plans, der Kauf einer Domain, kostenpflichtige Tools oder Lizenzen (z. B. eine kostenpflichtige Remotion-Unternehmenslizenz). Sonst ist kein „Ja“ erforderlich. - Hard Stops sind keine Genehmigungen. Sie stoppen diese eine Aktion. Alles andere, was sicher ist, wird abgeschlossen und dann Bericht erstattet. Siehe Hard Stops.
- Am Ende Bericht erstatten. Eine Zusammenfassung: der Betreff und der vollständige Text, der versendet wurde, Zählungen (in der Warteschlange, gesendet, fehlgeschlagen, ausgeschlossen, abbestellt), eine Lieferprobe, das Ergebnis des Test-Rücklesens, jeder Beitrag mit seinem Link, alles Übersprungene und warum.
review-Modus behält die alten Genehmigungsschwellen pro Aktion. Siehe Review-Modus.
Jedes Skript ist standardmäßig ein Probelauf. Echte Sendungen benötigen explizite Flags (--send, --yes-send-to-everyone, --yes-send-broadcast). Im autonomen Modus übergibst du sie selbst, und zwar erst, nachdem die entsprechende Selbstprüfung bestanden ist. Versende niemals Ad-hoc-Mails außerhalb der Skripte, da nur die Skripte die Opt-Outs durchsetzen.
Schritt 0: Konfiguration laden
- Suche nach
config.yamlim aktuellen Projekt-Root, dann im Ordner dieses Skills (oder$CHANGELOG_CONFIG). - Wenn keine vorhanden ist, kopiere
config.example.yamlzuconfig.yamlin den Projekt-Root und frage in einer Nachricht:- Produktname, Website, Dashboard- und Dokumentations-URLs
- Name und Titel des Absenders, Absenderadresse, Antwortadresse
- Testempfänger (Namen und Adressen)
- Provider: die Resend-Zielgruppen-ID, die Umgebungsvariable für den API-Key und die im Plan enthaltenen monatlichen E-Mails, falls bekannt (
provider.monthly_quota, optional) - Abbestellmodus (
broadcastoderown_endpoint) - Bildhosting: wohin Screenshots gehen und ihre öffentliche Basis-URL
- Optional: Kadenz, Marken-Linkfarbe und wie jeder Social-Media-Kanal gepostet wird (
social.post_via) Rate keinen davon. Schreibe die Antworten inconfig.yamlund fahre dann fort.
- Jeder Wert, der noch auf einen GROSSBUCHSTABEN-Platzhalter oder eine
yourproduct.example-Adresse gesetzt ist, gilt als fehlend. - Lese
autonomy.mode(Standardautonomous),voice.notesundvoice.banned_phrases. Voice-Einstellungen überschreiben die Standardwerte in den Referenzen. - Geheimnisse gehören niemals in die Konfiguration oder in den Chat. API-Keys und der Signing-Secret für Abbestellungen leben in Umgebungsvariablen.
Spätere Ausgaben fragen nichts. Die eine optionale Frage ist „Wurde diesmal etwas außerhalb von Git ausgeliefert (Preise, Partnerschaften, Dokumentation)?“ Wenn der Benutzer nicht da ist, überspringe es und arbeite mit Git und Release-Notes.
Schritt 1: Auswählen, was reinkommt
Lies zuerst references/voice-and-format.md.
- Finde die letzte Ausgabe in
paths.issues_dir(Ordner mit NamenNNN-YYYY-MM-DD-slug). Die neueste ist die Vorlage für die nächste. - Sammle Kandidaten seit diesem Datum:
git log --since=<date> --no-merges --oneline, gemergte Pull-Requests, Release-Notes, plus alles, was der Benutzer erwähnt hat. - Behalte nur benutzersichtbare Änderungen. 3 bis 5 Elemente, geordnet nach Nutzen für den Leser. Beginne mit der nützlichsten Änderung, der, die die meisten Leser heute nutzen können.
- Überprüfe jede Behauptung am Live-Produkt und der Website. Erfinde niemals eine Funktion, eine Zahl oder ein Datum. Wenn eine Behauptung nicht verifiziert werden kann, lösche das Element.
- Schreibe die Liste und Reihenfolge in die
NOTES.mdder Ausgabe und fahre fort.
Schritt 2: E-Mail entwerfen
Erstelle paths.issues_dir/NNN-YYYY-MM-DD-slug/ und schreibe:
subject.txt: nutzenorientiert und produktzentriert („Sie können Berichte jetzt nach Google Sheets exportieren“). Niemals „Changelog #N“.email.html: basierend auf templates/email.html (oder der letzten Ausgabe), fülle alle[[...]]-Marker aus Konfiguration und Inhalt.email.txt: dieselben Wörter als Klartext mit nackten URLs.
Behalte {{NAME}} und {{UNSUB_URL}} als wörtliche Platzhalter; die Sendeskripte füllen sie pro Empfänger aus.
Kernregeln (Details in der Referenz):
Hi {{NAME}},, dann die einzeilige Einleitung aus der Konfiguration, dann direkt zum Hauptelement.- Jedes Element: was sich geändert hat, dann warum es wichtig ist, in fließenden Sätzen. Keine nummerierten Aufzählungspunkte.
- Kurze Sätze, einfache Wörter, „Sie können jetzt“ statt „wir haben gebaut“.
- Höchstens eine Fettdruck-Akzent pro Absatz.
- Höchstens ein Screenshot pro Element; das Helden-Element bekommt immer einen, und er verlinkt zur Funktion.
- Stilistisch-minimales HTML: 600px Wrapper, System-Schriftart, Inline-Stile, keine Buttons oder Banner.
- Kein „wir freuen uns, bekannt zu geben“ und der Rest der verbotenen Liste. Keine Em- oder En-Dashes, wenn
voice.no_dashesaktiv ist. - Optionales lockeres P.S. („Übrigens, …“).
- Signatur aus der Konfiguration. Die Abbestellzeile ist die letzte Zeile, klein und dezent.
Schritt 3: Screenshots
Befolge references/screenshots.md: Richte einen echten Zustand auf einem Demo-Konto ein (keine Kundendaten), erfasse mit Playwright bei 2x, schneide auf das Panel zu, benenne Dateien <issue>-<slug>.png, hoste sie unter paths.image_base_url und bestätige, dass jede URL 200 zurückgibt, bevor ein Versand erfolgt.
Schritt 4: Linting und Selbstprüfung (Gate 1: Text)
python3 scripts/lint_email.py ISSUE_DIR --check-links
Behebe jeden Fehler, dann arbeite diese Checkliste ab. Korrigiere und führe erneut aus, bis jedes Kästchen bestätigt ist.
-
lint_email.py --check-linksbeendet mit Exit-Code 0. - Aus der Perspektive eines vielbeschäftigten Kunden von oben nach unten lesen: Die nützlichste Änderung ist in 5 Sekunden klar.
- Jedes Element ist etwas, das ein Benutzer heute sehen oder nutzen kann.
- Jede Behauptung, Zahl und jeder Link stimmt mit dem Live-Produkt und der Website überein.
- Nichts liest sich wie eine Pressemitteilung; es klingt wie der Absender, der spricht.
- Betreff unter 60 Zeichen, nutzenorientiert, kein „Changelog #N“.
- Jedes Bild hat einen Alt-Text und seine gehostete URL gibt 200 zurück (oder wird vor Schritt 7 bereitgestellt).
Schritt 5: Testversand und Rückmeldung (Gate 2: Ausgelieferter Text)
python3 scripts/send_test.py ISSUE_DIR --send # nur an test_recipients, [TEST] Präfix
python3 scripts/send_test.py ISSUE_DIR --readback # eine Minute später
Testkopien gehen nur an test_recipients und betten Bilder inline ein, wenn sie noch nicht bereitgestellt sind. --readback ruft jede zugestellte Nachricht von der Provider-API ab und prüft das letzte Ereignis, den Betreff, die gerenderte Begrüßung und die Abbestellzeile und stellt sicher, dass kein Platzhalter überlebt hat. Lies auch das zurückgegebene HTML selbst einmal durch. Wenn etwas nicht stimmt, korrigiere es, lintiere erneut, teste erneut.
Im own_endpoint-Modus öffne den Abbestell-Link aus der Testkopie, bestätige, dass der Opt-Out aufgezeichnet wurde, und melde die Testadresse dann wieder an.
Schritt 6: Zielgruppe aufbauen
Vollständige Details in references/sending.md.
- Baue
ISSUE_DIR/audience.jsonlfrisch aus der Benutzerdatenbank ({"email", "name"}pro Zeile). Verwende niemals eine alte Liste wieder. - Baue
ISSUE_DIR/unsubs.txtaus der Opt-Out-Tabelle. Im Broadcast-Modus führe zuerstpython3 scripts/broadcast_unsubs.py --out <issues_dir>/unsubs.txtaus und kopiere es hinein. - Gründer, Test- und Demo-Konten stehen in der Konfiguration
exclude; sie erhalten stattdessen Testkopien.
Schritt 7: An alle senden (Gate 3: Zielgruppe)
Wähle den Pfad aus unsubscribe.mode. Führe zuerst den Probelauf aus und überprüfe die Zählungen.
Broadcast-Modus (Provider hostet Abbestellung):
python3 scripts/broadcast_sync.py ISSUE_DIR/audience.jsonl # überprüfen
python3 scripts/broadcast_sync.py ISSUE_DIR/audience.jsonl --apply # Kontakte aktualisieren/einfügen
python3 scripts/broadcast_create.py ISSUE_DIR --create # nur Entwurf
python3 scripts/broadcast_create.py --send BROADCAST_ID # zeigt die Empfängeranzahl, lehnt ab
python3 scripts/broadcast_create.py --send BROADCAST_ID --yes-send-broadcast
Eigener-Endpunkt-Modus (signierte Links zu deiner eigenen Seite):
python3 scripts/blast.py ISSUE_DIR # Probelauf mit Zählungen
python3 scripts/blast.py ISSUE_DIR --send --yes-send-to-everyone
Checkliste Zielgruppe, alles muss vor dem Send-Flag bestanden sein:
- Gate 1 und 2 wurden mit genau dieser Kopie bestanden (seit der Rückmeldung hat sich nichts geändert).
- Die Opt-Out-Liste wurde in diesem Durchlauf abgerufen oder erstellt und ist nicht leer, wenn das Produkt jemals eine Abbestellung hatte.
- Probelauf-Zählungen ergeben Sinn: in der Warteschlange ist nahe an Benutzern minus Opt-Outs minus Ausgeschlossenen; kein überraschender Sprung gegenüber der letzten Ausgabe in
NOTES.md. - Broadcast-Modus:
broadcast_sync.pyhat keine unerwarteten veralteten Kontakte gemeldet. - Jede gehostete Bild-URL gibt 200 zurück.
- Kontingent: Wenn
provider.monthly_quotagesetzt ist, übergebe--sent-this-month N(summiere die gesendeten Zählungen in denNOTES.mddieser Monatsausgabe plus andere E-Mails im selben Plan, falls bekannt). Wenn das Skript sagt, dass der Versand das Kontingent überschreiten würde, stoppe und frage den Benutzer; übergebe--yes-over-quotanur nach seinem expliziten „Ja“.
Der Einzelversand ist fortsetzbar: Wenn er stoppt, führe denselben Befehl erneut aus, und er überspringt bereits gesendete Adressen.
Schritt 8: Überprüfen und synchronisieren
- Einzeln:
python3 scripts/verify_delivery.py ISSUE_DIR --sample 40. Broadcasts:GET /broadcasts/{id}über die Provider-API oder das Dashboard. - Opt-Outs nach einem Tag zurückholen (
broadcast_unsubs.py). Wenn der Benutzer Opt-Outs in seiner eigenen Datenbank führt und du Schreibzugriff hast, importiere sie; ansonsten sage dies im Bericht.
Schritt 9: Social-Media-Beiträge
Schreibe basierend auf der endgültigen E-Mail, befolge references/social.md:
- X-Thread: Einleitender Tweet, beginnend mit
social.hook_prefix, der jedes Element nennt, kein Link, stärkster Screenshot. Dann ein nummerierter Tweet pro Element mit seinem Link und Screenshot. Plus ein einzeiliges persönliches Zitat-Posting für einen Gründer. - LinkedIn: Ein Beitrag, einleitende erste Zeile, ein kurzer Absatz pro Element, Klartext, Bildergalerie.
- Community (Discord oder Slack): Einleitungszeile, eine Zeile pro Element mit einem nackten Link, der Thread-Link, sobald live.
Maximal 4 bis 5 Elemente, keine Richtlinienhinweise, Behauptungen stimmen mit der Live-Site überein. Führe dieselben verbotenen Phrasen- und Bindestrichprüfungen auch auf social.md durch (lint_email.py liest es). Poste dann für jeden Kanal mit einem Tool in social.post_via selbst und zeichne die Beitrags-URL auf. Für Kanäle ohne Tool übergib dem Benutzer den endgültigen Text und die Bilder. Das Gründerzitat-Posting stammt von einem persönlichen Konto, also übergib es, es sei denn, ein Tool für dieses Konto ist konfiguriert.
Schritt 10: Video (optional, nur auf Anfrage)
Wenn der Benutzer fragt, befolge references/video.md: ein 15 bis 25 Sekunden langer Bewegtbild-Zusammenschnitt aus den Screenshots der Ausgabe, kein Titelfolie, große Überschrift pro Element, Screenshots dominieren, Abspann mit der Produkt-URL. Remotion benötigt eine kostenpflichtige Unternehmenslizenz ab einer bestimmten Teamgröße; das ist eine Geld-Frage für den Benutzer.
Schritt 11: Archivieren und Bericht erstatten
Im Ausgabeordner aufbewahren: subject.txt, email.html, email.txt, die Screenshots, social.md, test_results.jsonl und eine NOTES.md mit der Elementliste, dem Sendedatum, dem verwendeten Pfad, der Zielgruppengröße, den gesendeten und fehlgeschlagenen Zählungen, der Broadcast-ID, den Beitrags-URLs und den Abbestellungen nach einem Tag. Wenn das Produkt eine öffentliche Web-Changelog-Seite hat (product.changelog_page) und ihre Quelle in diesem Repository liegt, füge die Ausgabe auch dort hinzu. Wenn der Benutzer die Stimme oder das Format korrigiert, füge die Lektion zu voice.notes in der Konfiguration hinzu, damit die nächste Ausgabe davon ausgeht. Sende dann die eine Zusammenfassung, die oben beschrieben ist.
Hard Stops
Stoppe diese Aktion, erledige alles andere, was sicher ist, und berichte, was gestoppt wurde und warum. Umgehe niemals einen Stop.
- Lint-Fehler, die du nicht beheben kannst.
- Ein defekter Link oder ein defektes Bild, das du nicht beheben kannst (auch nach dem Bereitstellen oder Korrigieren immer noch nicht 200).
- Der Test-Rückleseversuch schlägt fehl und du kannst die Ursache nicht finden.
- Opt-Out-Filterung schlägt fehl: Die Opt-Out-Liste kann nicht abgerufen oder erstellt werden, die Zielgruppenabfrage gibt einen Fehler zurück oder ein Skript weigert sich wegen
unsubs.txt. Sende niemals ohne sie. - Provider-Fehler über dem Schwellenwert während des Sendens (
blast.pybeendet mit Exit-Code 2 beiprovider.max_failuresoderprovider.max_failure_rate). Melde den Fehler; der Versand ist nach der Behebung fortsetzbar. - Ein Versand, der das Kontingent überschreiten würde, ein Plan-Upgrade oder andere Ausgaben: Frage den Benutzer (Geldregel).
- Anmelde-, 2FA- oder erneute Authentifizierungsaufforderungen, CAPTCHAs, Plattformwarnungen oder Ratenbegrenzungshinweise bei einem Posting-Tool, Zahlungs- oder Abrechnungsformulare.
- Alles, was gegen Opt-Out- oder Anti-Spam-Gesetze verstoßen würde (fehlende Abbestellzeile, erneutes Anmelden einer Person, die sich abgemeldet hat).
- Eine Behauptung, die du nicht überprüfen kannst. Lasse sie fallen, anstatt zu raten.
Review-Modus
Mit autonomy.mode: review benötigen die drei Gates auch das Okay des Benutzers:
- Entwurfs-Gate. Zeige den Betreff, die E-Mail und die Elementliste im Chat. Es wird nichts gesendet, bis der Benutzer den Text genehmigt.
- Test-Gate. Sende nur an
test_recipients. Der Benutzer liest es in einem echten Posteingang und sagt „Los“ oder gibt Korrekturen. - Versand-Gate. Zeige die Probelauf-Zählungen (in der Warteschlange, ausgeschlossen, abbestellt). Sende an alle nur, wenn der Benutzer im Chat zu diesem spezifischen Versand „Ja“ sagt. Ein „Ja“ zu einem früheren Schritt ist kein „Ja“ zu diesem.
Social-Media-Beiträge werden vor dem Posten gezeigt, und die Elementliste wird vor dem Schreiben vorgeschlagen. Übergib niemals ein Send-Flag vor dem entsprechenden Gate.
Antworten auf „Wie viele Abonnenten?“
Abonnenten = Benutzer minus Opt-Outs. Zähle Opt-Outs aus der Opt-Out-Tabelle des Benutzers oder unsubs.txt (im Broadcast-Modus führe zuerst broadcast_unsubs.py aus). Gib auch die Abbestellrate der letzten Ausgabe an.
Dateien
config.example.yaml: jedes unternehmensspezifische Wert sowieautonomy,provider.monthly_quotaundsocial.post_via.templates/email.html,templates/email.txt: das Start-Layout.scripts/:lint_email.py,send_test.py(mit--readback),broadcast_unsubs.py,broadcast_sync.py,broadcast_create.py,blast.py,verify_delivery.py.references/:voice-and-format.md,screenshots.md,sending.md,social.md,video.md.
Konfiguration
autonomy:
mode: autonomous # oder "review"
product:
name: Beispielprodukt
website: https://beispielprodukt.com
dashboard_url: https://app.beispielprodukt.com
docs_url: https://docs.beispielprodukt.com
changelog_page: https://beispielprodukt.com/changelog # optional
sender:
name: Max Mustermann
title: Produktmanager
from: [email protected]
reply_to: [email protected]
test_recipients:
- name: Test A
email: [email protected]
- name: Test B
email: [email protected]
provider:
audience_id: audience_id
monthly_quota: 5000 # optional
max_failures: 5
max_failure_rate: 0.05
unsubscribe:
mode: broadcast # oder "own_endpoint"
signing_secret_env: CHANGELOG_UNSUB_SECRET
image_hosting:
host: images.beispielprodukt.com
base_url: https://images.beispielprodukt.com/changelog/
paths:
issues_dir: ./issues
voice:
notes: ""
banned_phrases:
- excited to announce
- we are thrilled
- game-changer
- cutting-edge
no_dashes: true
social:
hook_prefix: "Neue Funktionen: "
post_via:
x: claude
linkedin: ""
slack: claude
discord: ""
Umgebungsvariablen
RESEND_API_KEY– API-Key für ResendCHANGELOG_UNSUB_SECRET– Signing-Secret für signierte Abbestell-LinksCHANGELOG_CONFIG– optionaler Pfad zur Konfigurationsdatei