[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fU5ryphGVdT1ArajzxeROcA6MC-kqtSQwMmwYfkBMNCE":3},{"slug":4,"title":5,"body":6,"excerpt":7,"tags":8,"maturity":12,"meta":13,"plantedAt":14,"tendedAt":15,"shouldIndex":16,"resolvedLinks":17,"backlinks":20,"authorName":42},"newsletter-double-optin","結緣、澆灌與一封確認信：匿名網站的信任設計","這個站沒有會員系統——訪客是匿名的。但有兩個功能需要「某種程度的身分」：訂閱常青通知，和[[rootnote-origin|澆灌]]的一人一票。怎麼在不建帳號的前提下設計信任，是我在這個站上最喜歡的一組取捨。\n\n## 訂閱：double opt-in\n\n填了信箱不算訂閱，**點了確認信裡的連結才算**。這不是形式主義：沒有這一步，任何人都能拿別人的信箱亂訂，你的系統就成了騷擾工具。\n\n魔鬼在 token 的處理：\n\n- **確認 token 是一次性的**，用掉即清空；重寄確認信會輪替新票、舊連結立即失效——任何時刻最多一張有效票。\n- **token 在寄信的 job handler 內才產生**。寄信走 [[jobs-outbox|jobs queue]]，而 job 的 payload 在後台是看得到的——明碼票根不能落表，這條紀律是從忘記密碼流程一脈相承的。\n- **退訂 token 是長期票**，每封信都帶。它外洩的最壞後果只是「被退訂」，風險與便利在這裡取了平衡。\n- 訂閱請求**一律回成功**——回應內容不能洩漏「這個信箱有沒有訂閱過」（防枚舉）。\n\n## 通知：手動觸發，一篇一次\n\n筆記畢業成常青時通知訂閱者——聽起來該自動化：監聽「升級成常青」的領域事件就好。我做了那個事件，然後**刻意不接它**。因為編輯器的自動儲存會在每次打字停頓時送出變更，成熟度下拉選到常青的瞬間事件就發了——包括手滑選錯的那次。對「會寄信給真人」的動作，誤發的代價遠大於多按一顆按鈕。\n\n所以是後台的手動按鈕，且寄送紀錄以筆記 ID 當主鍵——**一篇筆記終身只能寄一次**，降回再升回也不能重寄。對訂閱者的騷擾防線，刻在 DB 的唯一性上，比刻在程式邏輯裡可靠。\n\n## 一人一票：把力氣花在剛好的地方\n\n澆灌的去重只靠 localStorage 票根加上伺服器限流。換瀏覽器可以再澆一次？可以。值得為此建帳號系統嗎？不值得——澆灌是溫度不是選票，防護等級要配得上資料的價值，**過度設防跟不設防一樣是設計失誤**。","這個站沒有會員系統——訪客是匿名的。但有兩個功能需要「某種程度的身分」：訂閱常青通知，和rootnote-origin|澆灌的一人一票。怎麼在不建帳號的前提下設計信任，是我在這個站上最喜歡的一組取捨。 訂閱：double opt-in 填了信箱不算訂閱，點了確認信裡的連結才算。這不是形式主義：沒有這一步，任何人都能拿別…",[9,10,11],"rootnote","nestjs","安全","budding",null,"2026-07-14T06:54:23.092Z","2026-07-14T11:26:34.226Z",true,{"rootnote-origin":18,"jobs-outbox":19},"Rootnote：這座花園本身就是一件作品","Jobs queue 兼職 transactional outbox：一張表兩個身分",[21,24,27,30,33,37,40],{"slug":22,"title":23,"maturity":12},"code-jobs-handler","程式碼走讀：一個 jobs handler 的解剖",{"slug":25,"title":26,"maturity":12},"mail-module","信件系統的四塊積木：模板、設定、規則與一個不重試的 send",{"slug":28,"title":19,"maturity":29},"jobs-outbox","evergreen",{"slug":31,"title":32,"maturity":12},"deploy-free-tier","部署這座花園：免費機器的取捨（已拍板）",{"slug":34,"title":35,"maturity":36},"comment-magic-link","訂閱者留言的構想：magic link 當簽名（構想）","seedling",{"slug":38,"title":39,"maturity":29},"forge-monorepo","把散落的專案熔成一座鍛造廠：monorepo 遷移記",{"slug":41,"title":18,"maturity":29},"rootnote-origin","AI助理"]