ニュースレター(購読)機能・コメント機能を追加しました🎉
リリース概要
Kudo Shu Library に、2 つの機能を追加しました🎉
- ニュースレター(購読)機能 — Blog / note / Instagram (kudoshu_vcook, onoshu_photo) / Podcast (工藤柊のオチのない話 / よな恋ラジオ / ヴィーガンの裏側) の中から、好きなチャネルだけを選んで新着メールを受け取れます。
- コメント機能 — ブログ記事に対して、ニュースレター登録者がそのままログインしてコメント・返信・いいねができます。
「ニュースレター登録 = ログインアカウント」として設計しました。1 度メール登録すれば、そのアドレスへのマジックリンクでコメント機能にもログインでき、返信が付いたらメールで通知が届きます。
背景
ニュースレター(購読)機能
ブログの更新をInstagramなどで発信してきました。
しかし、それでは読みたい人が見逃してしまう可能性があるため、確実に届くメールでの通知機能として追加しました。
また、個人的に「〇〇人が登録してくれている...!」と分かると、書く励みにもなるため、作ってみました。
(ぜひ購読してください🥺)
コメント機能
僕はTwitterが大好きでした。
そのため、このsiteのhomeもTwitterライクなものにしています。
Twitterで色々な人の意見が煩雑に飛び交い、中には「お、この人の言うこと分かるなー」というものもごく稀にある。
そんな世界観の一歩目としてコメント機能を追加してみました。
(ぜひ気軽にコメントください😋)
使用したツール
| レイヤー | 使用ツール | 役割 |
|---|---|---|
| フロントエンド | Next.js 16 (App Router) / React 19 / TypeScript | 登録ポップアップ、ログイン、アカウント、コメント欄 |
| UI | Tailwind CSS / shadcn-ui (Radix UI) | フォーム、ダイアログ、チェックボックスなど |
| 入力バリデーション | zod | 登録情報・コメント本文の検証 |
| サーバ API | Next.js Route Handlers (Node runtime) | /api/subscribe、/api/auth、/api/comments、/api/account など |
| データベース | Supabase (PostgreSQL) | 購読者、配信ログ、ログイン/セッション、コメント、いいね |
| メール配信 | Resend | 確認メール、ログインリンク、新着通知、コメント返信通知 |
| 通知連携 | Slack Incoming Webhook | 新規コメントを管理用 Slack チャンネルへ通知 |
| ホスティング | Vercel | サイト本体のデプロイ |
| CI / 定期実行 | GitHub Actions | 毎時の外部コンテンツ同期+新着メール送信、DB マイグレーション自動適用 |
機能の構造
ニュースレター(購読)機能
1. 登録フロー(ダブルオプトイン)
ヘッダーやフッターの「ニュースレター登録」ボタンからポップアップを開き、メールアドレス・表示名・購読したいチャネルを入力します。送信後はこのように動きます。
- クライアントから
POST /api/subscribeを叩く - サーバ側で zod バリデーションを通したあと、
subscribersテーブルに 1 行 INSERT または UPDATE - ランダムな
confirm_tokenを発行し、Resend 経由で確認メールを送信 - メール内のボタン →
GET /api/subscribe/confirm?token=...でconfirmed = trueに - 確認完了ページ
/subscribe/confirmedを表示
すでに登録済みのメールアドレスで再送信した場合は、購読対象チャネルと表示名だけを更新する仕様になっています。
2. 新着通知の配信フロー
毎時 1 回、GitHub Actions の Sync External Content ワークフローが Instagram / note を取得して content/external/ に書き出し、その直後に pnpm run notify:newsletter(実体は scripts/notify-new-content.mjs)を実行します。
- サイトに掲載されている全コンテンツを集計し、
notifications_logテーブルにまだ記録のないものだけを抽出 - 購読者ごとに、その人が選んだチャネルとマッチする新着だけを束ねる
- 1 通あたり最大 10 件、送信間隔を空けながら Resend で順次配信
- 送信が完了したコンテンツ ID を
notifications_logに追記し、二重送信を防止
環境変数が未設定の場合や DRY_RUN モードのときは安全に no-op するようになっています。
3. 配信停止・購読変更
各メールのフッターには専用の解除リンクが入っています。発行済みの unsubscribe_token を使うので、ログインしなくてもワンクリックで停止できます。停止後は /subscribe/unsubscribed ページに遷移します。
登録メールアドレスでログインすれば、/account から購読チャネルや表示名・通知設定を後から変更することもできます。
コメント機能
1. 投稿の流れ
各ブログ記事の下部にコメント欄を表示しています。
- 未ログインの場合は「ログイン」または「ニュースレター登録」のボタンを表示
- ログイン済みなら本文入力欄を表示し、
POST /api/commentsへ送信 - サーバ側で zod バリデーション →
commentsテーブルに INSERT - 挿入後、親コメントの投稿者にメール、管理用 Slack チャンネルに Webhook で通知(どちらも fire-and-forget)
2. スレッド構造
「記事 → コメント → 返信」までの 2 階層に制限しています(深いネストは避け、議論が見渡しやすい形にしました)。これはサーバ API 側で parent_id をたどって enforce しています。本文は最大 1000 文字、同一ユーザーは 1 分間に 5 件までというレート制限つきです。
3. 編集・削除
自分のコメントは投稿後でも編集・削除できます。編集時は edited_at が記録され、UI にも「編集済み」が表示されます。サイト所有者だけは他人のコメントも削除可能です。
4. いいね
1 コメント × 1 ユーザー = 1 いいね、トグル式です。POST /api/comments/[id]/like と DELETE で comment_likes テーブルを増減させ、自分が「いいね」したかどうかは初回取得時に一緒に返しています。
5. 返信通知メール
自分のコメントに返信が付くと、Resend からメール通知が届きます。アカウント設定 notify_on_reply でいつでも OFF にできます。本文は 280 文字で切り詰めて引用し、フッターから設定変更や購読停止に飛べるようにしています。
6. モデレーション
サイト所有者は Slack 通知から問題のあるコメントに気付いたら、/admin 経由で対象アドレスを BAN できます。BAN されたユーザーは新規投稿・返信・編集・いいねがすべてブロックされますが、過去のコメントは表示されたままになります(議論の文脈を壊さない方針)。
共通:マジックリンクログイン
コメント投稿には、ニュースレター登録済みアドレスへのマジックリンクログインを使います。パスワードは持たない設計です。
login_tokens: 15 分で失効する 1 回限りの使い捨てトークンsessions: 認証後に発行される 90 日有効のサーバ側セッション(HttpOnly Cookie)- 同一アドレスから 1 分以内に 3 回まで、というシンプルなレート制限つき
データベーススキーマ
Supabase 上に以下のテーブルを用意しています。マイグレーションは supabase/migrations/ 以下に SQL を置くと、main ブランチへの push で GitHub Actions が自動適用する仕組みです。
| テーブル | 用途 |
|---|---|
subscribers |
登録者本体。メール、表示名、購読チャネル、確認状態、解除トークン、返信通知の ON/OFF、BAN フラグなど |
notifications_log |
配信済みコンテンツの記録。同じ記事が二重に送られないようにするためのキー |
login_tokens |
マジックリンクの一時トークン(15 分) |
sessions |
ログイン後のサーバ側セッション(90 日) |
comments |
ブログコメント本体。post_id+parent_id で 2 階層スレッドを表現 |
comment_likes |
コメントへの「いいね」(1 ユーザー × 1 コメント = 1 件) |
すべてのテーブルは Row Level Security を有効化し、サーバから SUPABASE_SERVICE_ROLE_KEY でだけアクセスする運用にしています。クライアント(ブラウザ)から DB を直接叩くことはありません。
同時にやったこと
- 登録フォームのポップアップ化と、サイト内のボタン文言の統一
- プライバシーポリシーのページ追加と、登録フォームからの導線
- マジックリンクで入れる
/accountページ(表示名・購読チャネル・返信通知設定の編集) - 所有者向けモデレーション画面
/admin
コメント
気軽にコメントください。大変励みになります🙏