CommunitySchreiben & Editierengithub.com

mariagorskikh/product-changelog

Erstelle und versende einen regelmäßigen Product-Changelog per E-Mail an deine Nutzer, verfasse und poste dazu passende X-Threads, LinkedIn-Beiträge und Community-Ankündigungen (Discord oder Slack) und skizziere optional ein animiertes Changelog-Video.

Was ist product-changelog?

Dieses Skill automatisiert den gesamten Workflow für Product-Changelogs, von der Erstellung über den Versand bis hin zur Veröffentlichung auf sozialen Kanälen. Es richtet sich an Entwickler und Produktteams, die regelmäßige Produkt-Updates effizient kommunizieren möchten. Der Skill unterstützt die Plattformen Claude Code, Cursor und Codex und führt nach einer einmaligen Einrichtung alle Schritte autonom aus: Er wählt die relevanten Änderungen aus (3-5 nutzersichtbare Neuerungen), erstellt die E-Mail aus einer Vorlage, erstellt Screenshots, führt Testversendungen mit Rückmeldung durch, baut die Empfängerliste auf, versendet die E-Mail unter Einhaltung von Opt-Outs und veröffentlicht die Inhalte auf X, LinkedIn und Discord/Slack. Nur kostenpflichtige Aktionen (z. B. Überschreitung des Sendekontingents) erfordern eine explizite Zustimmung. Der Skill nutzt Resend für den E-Mail-Versand (Broadcast oder Einzelversand) und validiert jede Ausgabe durch Selbstprüfungen, bevor sie live geht.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mariagorskikh/product-changelog/tree/HEAD

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

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.yaml wird 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

  1. Suche nach config.yaml im aktuellen Projekt-Root, dann im Ordner dieses Skills (oder $CHANGELOG_CONFIG).
  2. Wenn keine vorhanden ist, kopiere config.example.yaml zu config.yaml in 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 (broadcast oder own_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 in config.yaml und fahre dann fort.
  3. Jeder Wert, der noch auf einen GROSSBUCHSTABEN-Platzhalter oder eine yourproduct.example-Adresse gesetzt ist, gilt als fehlend.
  4. Lese autonomy.mode (Standard autonomous), voice.notes und voice.banned_phrases. Voice-Einstellungen überschreiben die Standardwerte in den Referenzen.
  5. 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 Namen NNN-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.md der 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_dashes aktiv 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-links beendet 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.jsonl frisch aus der Benutzerdatenbank ({"email", "name"} pro Zeile). Verwende niemals eine alte Liste wieder.
  • Baue ISSUE_DIR/unsubs.txt aus der Opt-Out-Tabelle. Im Broadcast-Modus führe zuerst python3 scripts/broadcast_unsubs.py --out <issues_dir>/unsubs.txt aus 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.py hat keine unerwarteten veralteten Kontakte gemeldet.
  • Jede gehostete Bild-URL gibt 200 zurück.
  • Kontingent: Wenn provider.monthly_quota gesetzt ist, übergebe --sent-this-month N (summiere die gesendeten Zählungen in den NOTES.md dieser 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-quota nur 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.py beendet mit Exit-Code 2 bei provider.max_failures oder provider.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:

  1. Entwurfs-Gate. Zeige den Betreff, die E-Mail und die Elementliste im Chat. Es wird nichts gesendet, bis der Benutzer den Text genehmigt.
  2. Test-Gate. Sende nur an test_recipients. Der Benutzer liest es in einem echten Posteingang und sagt „Los“ oder gibt Korrekturen.
  3. 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 sowie autonomy, provider.monthly_quota und social.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 Resend
  • CHANGELOG_UNSUB_SECRET – Signing-Secret für signierte Abbestell-Links
  • CHANGELOG_CONFIG – optionaler Pfad zur Konfigurationsdatei

Verwandte Skills