產品變更日誌
一個定期發送的電子郵件,用於告知用戶產品有哪些變更。它以人性化的口吻撰寫,安全地發送,然後再被重複用於社群貼文。對於每一期內容,電子郵件是唯一的權威來源;所有其他管道都是從它摘錄而成。
此技能的運作方式
- 只問一次,且問在前面。 在第一次執行時(或當設定缺失、必要值為空白時),以一個簡短的批次一次性詢問設定問題,寫入
config.yaml,然後繼續執行,不再提問。 - 然後自動完成所有後續步驟。 在
autonomy.mode: autonomous(預設值)模式下,你會自動挑選項目、起草、檢查、自我審查、截圖、測試發送、讀取測試結果、建立受眾、試發送、發送給所有人、驗證、拉取退訂名單、撰寫並發布社群貼文,以及歸檔。中間不需要任何核准步驟。每個對外的動作都會先通過一個自我審查閘門。 - 只有花錢的事才需要核准。 任何需要花費金錢的動作,都必須在對話中獲得使用者明確的「好」才能執行,這在所有模式下都適用:例如發送量會超過供應商方案內含配額 (
provider.monthly_quota)、升級供應商方案、購買網域、付費工具或授權(例如付費的 Remotion 公司授權)。除此之外,其他任何動作都不需要使用者說「好」。 - 強制停止不是核准。 它只是中止這一個動作。繼續完成其他安全的任務,然後回報。請參閱強制停止。
- 最後回報。 提供一份摘要:包含已發送的主旨和完整內文、各項統計數字(佇列中、已發送、失敗、排除、已退訂)、送達樣本、測試回讀結果、每則貼文及其連結,以及任何被跳過的項目及其原因。
review模式 會保留舊有的逐動作核准閘門。請參閱審查模式。
每個腳本預設都是試發送。真正的發送需要明確加上參數(--send、--yes-send-to-everyone、--yes-send-broadcast)。在自主模式下,由你自行加上這些參數,並且必須在對應的自我審查閘門通過之後。切勿使用腳本之外的方式臨時發送郵件,因為只有這些腳本會強制檢查退訂名單。
步驟 0:載入設定
- 在目前專案的根目錄下尋找
config.yaml,然後在這個技能所屬的資料夾中尋找(或使用$CHANGELOG_CONFIG環境變數指定的路徑)。 - 如果找不到,將
config.example.yaml複製到專案根目錄下的config.yaml,並在一則訊息中詢問以下事項:- 產品名稱、網站、儀表板和文件網址
- 寄件者名稱、職稱、寄件地址、回覆地址
- 測試收件者(姓名和電子郵件地址)
- 供應商:Resend 的受眾 ID、儲存 API 金鑰的環境變數名稱,以及方案的每月包含郵件數量(如果他們知道的話)(
provider.monthly_quota,選擇性) - 退訂模式(
broadcast或own_endpoint) - 圖片託管:截圖的存放位置及其公開的基礎網址
- 選擇性項目:發送頻率、品牌連結顏色,以及每個社群管道的發布方式(
social.post_via) 請不要猜測任何資訊。將答案寫入config.yaml,然後繼續。
- 如果任何值仍設定為大寫的佔位符或類似
yourproduct.example的地址,則視為遺失。 - 讀取
autonomy.mode(預設為autonomous)、voice.notes和voice.banned_phrases。語音設定會覆蓋參考文件中的預設值。 - 機密資訊絕不能放在設定檔或對話中。API 金鑰和退訂簽章密碼必須儲存在環境變數中。
之後的期數則無需再詢問。唯一一個選擇性問題是:「這次有沒有透過 Git 以外的方式上線的變更(例如定價、合作關係、文件)?」如果使用者不在,請跳過這個問題,直接從 Git 和發布說明中取得資料。
步驟 1:挑選要納入的項目
首先閱讀 references/voice-and-format.md。
- 在
paths.issues_dir資料夾中尋找上一期的目錄(目錄命名格式為NNN-YYYY-MM-DD-slug)。最新的一期將作為下一期的範本。 - 從上一期日期之後收集可能的候選項目:
git log --since=<date> --no-merges --oneline、已合併的拉取請求、發布說明,以及使用者提到的任何內容。 - 只保留使用者可見的變更。3 到 5 個項目,並根據對讀者的實用性進行排序。以最有用的變更開頭,也就是最多讀者今天就能使用的變更。
- 核對每一項宣稱,確認與實際產品和網站一致。絕對不能憑空創造功能、數字或日期。如果某項宣稱無法驗證,就刪除該項目。
- 將列表和順序寫入該期目錄下的
NOTES.md檔案,然後繼續。
步驟 2:起草電子郵件
建立路徑 paths.issues_dir/NNN-YYYY-MM-DD-slug/ 並寫入以下檔案:
subject.txt:以利益為導向且產品優先(「您現在可以將報表匯出到 Sheets」)。絕對不能使用「Changelog #N」。email.html:從 templates/email.html(或上一期)複製,並根據設定和內容填入每一個[[...]]標記。email.txt:與 HTML 版本相同的文字內容,但使用純文字格式,並直接顯示網址。
保留 {{NAME}} 和 {{UNSUB_URL}} 作為文字佔位符;發送腳本會在發送時針對每位收件者填入實際資料。
核心規則(詳細內容請參考文件):
Hi {{NAME}},開頭,接著是設定檔中的一行開場白,然後直接進入最重要的項目。- 每個項目:先說明變更了什麼,然後說明為什麼重要,使用流暢的句子。不要使用標籤式條列。
- 使用簡短的句子、平實的詞彙,用「您現在可以」取代「我們打造了」。
- 每個段落最多只能有一個粗體強調。
- 每個項目最多一張截圖;最重要的項目一定會有一張,而且圖片要連結到該功能。
- 風格簡潔的 HTML:600px 寬的容器、系統字型、內嵌樣式,沒有按鈕或橫幅。
- 不能使用「興奮地宣布」以及其他禁用詞彙清單中的詞句。如果
voice.no_dashes設定為開啟,則不能使用長破折號或短破折號。 - 可選的輕鬆附註(「BTW, ...」)。
- 使用設定檔中的署名。退訂提示行為最後一行,字型要小且顏色柔和。
步驟 3:截圖
遵循 references/screenshots.md 中的步驟:在演示帳號上設定真實狀態(不能是客戶資料),使用 Playwright 以 2 倍解析度進行截圖,裁剪到面板範圍,將檔案命名為 <issue>-<slug>.png,上傳到 paths.image_base_url 路徑,並在進行任何發送前,確認每個網址都能回傳 200 狀態碼。
步驟 4:檢查與自我審查(閘門 1:內容)
python3 scripts/lint_email.py ISSUE_DIR --check-links
修正每個錯誤,然後逐一核對此檢查清單。修正並重新執行,直到每個項目都通過。
-
lint_email.py --check-links以狀態碼 0 結束。 - 以一個忙碌的客戶的角度從頭到尾閱讀:最有用的變更能在 5 秒內清楚呈現。
- 每個項目都是使用者今天就能看到或使用的功能。
- 每項宣稱、數字和連結都與實際產品和網站相符。
- 內容讀起來不像新聞稿;聽起來像是寄件者在說話。
- 主旨不超過 60 個字元、以利益為導向,不能是「Changelog #N」。
- 每張圖片都有替代文字,且其託管網址回傳 200 狀態碼(或在步驟 7 之前會準備好)。
步驟 5:測試發送與回讀(閘門 2:送達的副本)
python3 scripts/send_test.py ISSUE_DIR --send # 只發送給 test_recipients,加上 [TEST] 前綴
python3 scripts/send_test.py ISSUE_DIR --readback # 一分鐘後
測試副本只發送給 test_recipients,如果圖片尚未部署,則會內嵌圖片。--readback 會從供應商 API 拉取每封送達的郵件,檢查最後的事件、主旨、渲染後的問候語和退訂提示行,並確認沒有遺漏任何佔位符。同時,你自己也要親手閱讀一次回傳的 HTML 內容。如果有任何問題,修正後重新檢查並重新測試。
在 own_endpoint 模式下,點擊測試副本中的退訂連結,確認退訂已被記錄,然後重新訂閱測試用的電子郵件地址。
步驟 6:建立受眾
詳細內容請參閱 references/sending.md。
- 根據使用者的資料庫全新建立
ISSUE_DIR/audience.jsonl(每行包含{"email", "name"})。不要重複使用舊的清單。 - 根據退訂資料表建立
ISSUE_DIR/unsubs.txt。在廣播模式下,首先執行python3 scripts/broadcast_unsubs.py --out <issues_dir>/unsubs.txt並將結果複製進去。 - 設定檔
exclude中的創辦人、測試帳號和演示帳號,會收到測試副本而非正式發送。
步驟 7:發送給所有人(閘門 3:受眾)
根據 unsubscribe.mode 選擇發送路徑。首先執行試發送並檢查統計數字。
廣播模式(供應商主機處理退訂):
python3 scripts/broadcast_sync.py ISSUE_DIR/audience.jsonl # 計畫
python3 scripts/broadcast_sync.py ISSUE_DIR/audience.jsonl --apply # 新增或更新聯絡人
python3 scripts/broadcast_create.py ISSUE_DIR --create # 僅建立草稿
python3 scripts/broadcast_create.py --send BROADCAST_ID # 顯示收件者數量,並拒絕
python3 scripts/broadcast_create.py --send BROADCAST_ID --yes-send-broadcast
自有端點模式(使用簽名連結導向你自己的頁面):
python3 scripts/blast.py ISSUE_DIR # 試發送並顯示統計
python3 scripts/blast.py ISSUE_DIR --send --yes-send-to-everyone
受眾檢查清單,在傳遞發送參數前必須全部通過:
- 閘門 1 和 2 已在此精確副本上通過(自回讀後無任何變動)。
- 退訂名單在此次執行中被拉取或建立,且如果產品曾經有過退訂,該名單不為空。
- 試發送的統計數字合理:佇列中的數量接近使用者總數減去退訂數及排除數;與上一期的
NOTES.md相比沒有異常的跳躍。 - 廣播模式:
broadcast_sync.py未回報任何意外的過時聯絡人。 - 每張託管圖片的網址都回傳 200 狀態碼。
- 配額:如果已設定
provider.monthly_quota,則傳入--sent-this-month N(這個月所有期數NOTES.md中記錄的已發送數量總和,加上如果已知的其他使用同方案的郵件數量)。如果腳本顯示發送將超出配額,請停止並詢問使用者;只有在使用者明確說「好」之後,才能傳遞--yes-over-quota參數。
逐次發送是可恢復的:如果中途停止,重新執行相同的指令,它會跳過已經發送過的地址。
步驟 8:驗證與同步
- 逐次發送:
python3 scripts/verify_delivery.py ISSUE_DIR --sample 40。廣播:透過供應商 API 或儀表板執行GET /broadcasts/{id}。 - 一天後拉取退訂資料(
broadcast_unsubs.py)。如果使用者將退訂資訊儲存在自己的資料庫中,且你有寫入權限,請匯入;否則請在報告中說明。
步驟 9:社群貼文
根據最終版的電子郵件內容,並遵循 references/social.md 撰寫:
- X 貼文串:以
social.hook_prefix開頭的鉤子貼文,點名所有項目,無連結,使用最強而有力的截圖。然後每個項目一篇編號貼文,包含其連結和截圖。再加上一則創辦人的一行個人引言貼文。 - LinkedIn:一篇貼文,第一行是鉤子,每個項目一個簡短段落,純文字,附上圖片庫。
- 社群(Discord 或 Slack):開場一行,每個項目一行並附上純文字連結,貼文串上線後再補上貼文串連結。
最多 4 到 5 個項目,無政策通知,宣稱需與實際網站相符。對產生的 social.md 執行相同的禁用詞彙和破折號檢查(lint_email.py 會讀取它)。然後,針對 social.post_via 中有設定工具的每個頻道,自行發布貼文並記錄貼文網址。對於沒有設定工具的頻道,將最終的文字和圖片交給使用者。創辦人的引言貼文來自個人帳號,因此除非該帳號已配置工具,否則請交給使用者處理。
步驟 10:影片(選擇性,僅在要求時執行)
如果使用者要求,請遵循 references/video.md:製作一個 15 到 25 秒的動畫,使用該期截圖,沒有標題投影片,每個項目有一個大標題,截圖為主,結尾加上產品網址。對於超過一定規模的團隊,Remotion 需要付費的公司授權;這是一個需要向使用者確認花費的問題。
步驟 11:歸檔與回報
在該期資料夾中保留以下檔案:subject.txt、email.html、email.txt、截圖、social.md、test_results.jsonl,以及一個內容包含項目列表、發送日期、使用路徑、受眾規模、成功和失敗數量、廣播 ID、貼文網址,以及一天後的退訂人數的 NOTES.md。如果產品有一個公開的網頁版變更日誌頁面(product.changelog_page)且其原始碼就在此儲存庫中,也請將該期內容新增進去。當使用者修正語氣或格式時,將學到的教訓加入設定檔中的 voice.notes,以便下一期能從中開始。然後,發送如開頭所述的一份摘要。
強制停止
停止該動作,完成所有其他安全的任務,然後回報是什麼被停止了以及原因。切勿繞過強制停止。
- 你無法修正的 lint 錯誤。
- 你無法修正的損壞連結或圖片(部署或修正後仍無法回傳 200 狀態碼)。
- 測試回讀失敗且你無法找出原因。
- 退訂過濾失敗:無法拉取或建立退訂名單、受眾查詢發生錯誤,或腳本因為
unsubs.txt的問題而拒絕執行。切勿在沒有退訂名單的情況下發送郵件。 - 發送過程中,供應商錯誤次數超過閾值(
blast.py在達到provider.max_failures或provider.max_failure_rate時會以狀態碼 2 退出)。回報錯誤;一旦問題解決,blast 是可恢復的。 - 發送將超出配額、方案升級或其他任何需要花費的情況:詢問使用者(金錢規則)。
- 發布工具出現登入、雙因子驗證或重新授權提示、CAPTCHA、平台警告或速率限制通知、付款或帳單表單。
- 任何可能違反退訂規定或反垃圾郵件法規的行為(缺少退訂提示行、重新訂閱已退訂的人)。
- 你無法驗證的宣稱。將其刪除,不要猜測。
審查模式
當 autonomy.mode: review 時,三個閘門也需要使用者的同意:
- 草稿閘門。 在對話中顯示主旨、電子郵件內容和項目列表。在使用者核准內容之前,不會發送任何東西。
- 測試閘門。 僅發送給
test_recipients。使用者在真實的收件匣中閱讀後,表示可以繼續或提出修正。 - 發送閘門。 顯示試發送的統計數字(佇列中、已排除、已退訂)。只有在使用者在對話中對該次特定發送表示「好」時,才會發送給所有人。對前面步驟的同意,不代表同意這一步。
社群貼文在發布前會先顯示,項目列表則在撰寫前提出。在對應的閘門通過之前,切勿傳遞發送參數。
回答「有多少訂閱者?」
訂閱者 = 使用者總數 - 退訂人數。從使用者的退訂資料表或 unsubs.txt 計算退訂人數(在廣播模式下,先執行 broadcast_unsubs.py)。也提供最新一期的退訂率。
檔案
config.example.yaml:包含每個公司特有的值,以及autonomy、provider.monthly_quota和social.post_via。templates/email.html、templates/email.txt:初始版面配置。scripts/:lint_email.py、send_test.py(包含--readback)、broadcast_sync.py、broadcast_create.py、broadcast_unsubs.py、blast.py和verify_delivery.py。references/:voice-and-format.md、screenshots.md、sending.md、social.md和video.md。