しんたろーのITアカデミー
開発日記

「1000件で止まってた」Claude Codeで1人開発。メルマガ配信の制限を知らずに青ざめた話。

「1000件で止まってた」Claude Codeで1人開発。メルマガ配信の制限を知らずに青ざめた話。
しんたろーしんたろー
18分で読めます
この記事の内容(目次)

※この記事は、Claude Codeで1人開発しているSNS運用SaaS「ThreadPost」の開発日記です。

SNS運用を自動化しませんか?

ThreadPostなら、投稿作成・画像生成・スケジュール管理までAIがサポート。

無料で始める

1000件で止まっていたメルマガ配信の真相

1000件で止まってた。知らんのかよ僕。

メルマガの配信ログを見て血の気が引いた。

1001人目以降に登録してくれたユーザーに、僕のメールは1通も届いていなかった。

原因はデータベースのデフォルト制限だ。

1000件の壁とAIによる予期せぬ進化
1000件の壁とAIによる予期せぬ進化

Supabaseのクエリは、ページネーションを実装しないと1000件で強制的に打ち切られる。

実際の配信対象は1,663人だった。

663人のユーザーが、僕のシステムから完全に切り捨てられていた。

動いているように見えて、実は止まっている。

これが1人開発で一番怖い。

技術的負債とAIの暴走を制御しながら、泥臭い検証でプロダクトを磨き上げた1週間だった。

雑な指示でアーキテクチャが消滅した夜

AIに「もっと速くして」と雑投げしたら、アーキテクチャごと書き換えられた。

投稿候補ページを開くたびに、画面の真ん中でくるくる回っていた。

僕はてっきり「ちょっとキャッシュ足しとけ」くらいで終わると思っていた。

ターミナルを開いて、Claude Codeに「もっと速くして」とだけ送った。

数分後、画面のくるくるが完全に消滅していた。

「fix: 投稿候補ページをServer Component化して初期データをサーバー側で取得」

こいつ、クライアント側のデータ取得を全廃しやがった。

Next.jsのApp Routerにおいて、クライアント側でfetchを繰り返すのはウォーターフォールを招く。

コンポーネントがマウントされてからAPIを叩き、応答を待ってから次のコンポーネントを描画する。

この連鎖が、ユーザー体験を著しく損なう典型的なアンチパターンだ。

従来のReact SPAでは、ローディングスピナーを表示しながらデータを待つのが当たり前だった。

しかし、これでは初期表示が遅くなり、SEOにも悪影響を及ぼす。

AIはそれを一瞬で見抜き、Server Componentに丸ごと書き換えてきた。

データ取得をサーバーサイドで完結させ、クライアントのJS実行量を極限まで削る。

HTMLが生成された状態でブラウザに届くため、ユーザーは一瞬でコンテンツを見ることができる。

APIキーの秘匿性も上がり、セキュリティとパフォーマンスが向上する。

データベースへの直接アクセスも可能になり、中間APIの構築すら不要になる。

僕がやったのは「速くしたい」という意図を投げただけだ。

どう実現するかはこいつが選んだ。

ただし、自分が書いたコードじゃないから、どこで何が起きてるか把握するのに結局1時間かかった。

しんたろーしんたろー:
企業なら設計会議で2週間飛ぶやつ。僕はコーヒー淹れてる間に終わった。でも自分が書いたコードじゃないから、どこで何が起きてるか把握するのに結局1時間かかった。

味を占めた僕は、さらに雑な指示を投げた。

「エラー出ても止まるな」

認証まわりがちょいちょい落ちていたからだ。

SPA遷移時にgetUser()は成功するが、JWTが不正でクエリが500エラーになる。

初回ナビゲーション時の認証タイミングがズレていた。

Supabaseの認証セッションは、ローカルストレージとCookieの両方で管理される。

クライアントサイドのルーターがページ遷移を検知した瞬間、まだ新しいトークンがセットされていない状態でAPIを叩きに行っていた。

全部バラバラのタイミングで、バラバラの理由で落ちていた。

「fix: クライアント認証失敗時にServer Actionフォールバックで確実にデータ取得」

「fix: クエリ500エラー時もServer Actionフォールバックを実行」

「fix: getUser→getSessionに変更+セッション無し時にServer Actionフォールバック」

怒涛のコミット連打。

動かしてみたら、エラーが出てもユーザーには何も見えない。

画面はそのままスムーズに動いている。

完璧だと思った。

でもログを見たら、静かにフォールバックが走りまくっていた。

「お前、毎回転んでるじゃないか」

根本原因を直す前に、エラーを隠蔽する仕組みだけが完成していた。

Server Actionへのフォールバックは、ネットワークリクエストのオーバーヘッドを増大させる。

本来ならミリ秒で終わるはずのキャッシュからのデータ取得が、毎回サーバーへのラウンドトリップを発生させていた。

次はキュレーション機能だ。

なんとなく「もっと精度ほしいな」と思って投げた。

「引用候補の質を上げて。スコアの精度も。探索も効率化して」

「refactor: キュレーターパイプラインを全面改善 — データ破壊防止+スコア精度向上+探索効率化」

パイプラインが全面的に書き変わっていた。

429レート制限でキュレーターが誤って無効化されるのを防ぐロジックが入っていた。

nullと空配列を厳密に区別する処理だ。

Math.random()をスコアから除去しトップ3からランダム選択。

フォロワー数チェックをGemini API呼び出し前に移動。

連続ミスの閾値を3から5に引き上げ。

無駄なAPIコールを削減するための最適化だ。

誤無効化率が12.5%から3.1%に下がった。

「fix: 引用候補をAI/SaaS/dev/SNS系に限定 — インプ枠を廃止」

引用候補がAIやSaaS系に絞られていた。

ジャンル無関係の記事が73%を占めていたからだ。

ブランドの一貫性を保つため、インプ枠という概念すら消し去られていた。

「feat: 他人記事の引用前にいいね+フォローを自動実行」

頼んでいないのに、引用前にいいねとフォローを自動実行する処理まで追加されていた。

1日6件の厳選記事著者のみが対象だ。

確かにそっちの方が質の高いネットワーク構築になる。

次からはAIの提案をそのまま受け入れる。たぶん。

1000人の壁と消えたログ

アーキテクチャが進化する一方で、足元はボロボロだった。

メルマガ配信システムが、1000件で止まっていた。

「fix: メルマガ配信対象の取得でSupabaseデフォルトlimit 1000を回避」

Supabaseのクエリは、デフォルトで1000件の制限がかかる。

知らなかった。

PostgRESTの仕様で、意図しない全件スキャンによるパフォーマンス低下を防ぐための保護機能だ。

データベースにおいて、インデックスを使わずに全レコードを読み込む操作はシステム全体を遅延させる。

特にクラウド環境では、1つの重いクエリが他のテナントのリソースまで圧迫してしまう。

そのため、API層で強制的にリミットをかけるのは理にかなった設計だ。

ページネーションを実装しないと、1001件目以降は容赦なく切り捨てられる。

実際の配信対象は1,663人だった。

ダッシュボードの数字を見て、コーヒーを吹き出しそうになった。

663人のユーザーに、僕のメールは1通も届いていなかった。

しかも、この制限はエラーを吐かない。

正常に1000件取得できたというステータスコード200を返してくる。

だから監視ツールのアラートも鳴らなかった。

しんたろーしんたろー:
動いてる=正しいと思って送信ログだけ見てた。届いてない人のログは当然残らない。生存者バイアスの極み。

しかも、それだけじゃなかった。

Claude Codeに「メルマガ周りまとめて見てくれ」と投げたら、5つ問題があると言ってきた。

確認したら全部本物だった。

「fix: メルマガ送信が存在しないmetadataカラムに依存していたバグを修正」

存在しないカラムを参照してクラッシュするバグ。

email_send_logsテーブルにmetadataカラムが存在しないため、ログのインサートや重複回避クエリが全て失敗していた。

newsletter_sent_atベースの判定に変更した。

「fix: メルマガ実装の5つの問題を修正」

送信成功前に記事を消費フラグに変えてしまうバグ。

サムネイル画像が型キャストのエラーで落ちていた問題。

引用ブロックの左ボーダーの色が統一されていなかった件まで直してきた。

一番ヤバかったのは、バウンス率苦情率のチェックが甘かったことだ。

「feat: メルマガ配信前にバウンス率・苦情率の安全チェックを追加」

「refactor: メルマガの安全チェックを既存のisSendingPausedに共通化」

メール配信において、バウンス率や苦情率を無視するのは危険だ。

AWS SESなどの送信ドメインのレピュテーションが即座に低下する。

スパム業者が大量の無効なアドレスにメールを送りつけるのを防ぐため、プロバイダは送信者の信頼性を常に監視している。

閾値を超えると、最悪の場合はアカウントがブラックリストに入れられる。

一般的に、バウンス率が5%を超えると警告が来て、10%でアカウントが一時停止されると言われている。

一度ブラックリストに入ると、解除申請は非常に面倒だ。

他の正常なメールすら届かなくなる。

今は直前24時間のバウンス率が5%、苦情率が0.1%を超えたら自動で止まる仕組みになった。

動いているように見えて、実は首の皮一枚で繋がっていただけだ。

1行のコードで透明人間になっていた

robots.txtで自分のCSSとJSを、Googleに読ませていなかった。

「fix: robots.txtから/_next/のDisallowを削除(GoogleがCSS/JSを読めずインデックス品質低下)」

Next.jsのデフォルト設定をそのまま使っていた。

ずっとそれを「まあいいか」で放置していた。

Googleのクローラーは、単にHTMLのテキストを読むだけではない。

Web Rendering Serviceを使って、JavaScriptを実行しページをレンダリングする。

最新のChromiumエンジンを内部で立ち上げ、ユーザーが実際に見るのと同じDOMツリーを構築してから評価するのだ。

CSSやJSファイルへのアクセスをブロックすると、ページが正しく描画されない。

レイアウトが崩れた状態のページとしてインデックスされてしまう。

モバイルフレンドリーテストに落ちる原因にもなる。

ボタンが重なってクリックできない、テキストが小さすぎるなどのペナルティを受ける。

インデックス時の品質評価が著しく低下する。

たまたまSearch Consoleのカバレッジを見て気づいた。

赤いエラーグラフを見て背筋が凍った。

直すのは1行削除で5秒だ。

でも気づかなかったら、どれだけコンテンツを作っても評価されない状態が続いていた。

5秒で直る1行のコードのために、僕のサイトはGoogleの検索結果で「透明人間」になっていた。

ただし、直したからといって急にアクセスが増えるわけでもなかった。

ここまで読んだあなたに

今なら無料で全機能をお試しいただけます。設定後はAIが投稿案を毎日生成。確認して選ぶだけ。

無料で始める

腕が3本あるキャラクター

AIが生成したキャラクターの腕が3本になっていた。

「fix: キャラバリデーションを日本語プロンプトに変更(英語だと腕3本を検知できない問題)」

英語プロンプトで「腕が2本あるか確認して」と指示していた。

Gemini Flash Liteは「2本です」と答えてバリデーションを通していた。

生成された画像を見ると、普通に腕が3本ある。

マルチモーダルAIは、画像をピクセル単位で理解しているわけではない。

特徴量のベクトルとして捉えているため、「腕の数」のような具体的な物理的制約のカウントを苦手とする。

特に軽量モデルでは、視覚的な推論能力が制限されている。

プロンプトを日本語に書き直したら、ちゃんと腕3本を弾くようになった。

ステップバイステップで腕と足を数えさせてから判定する方式に変更した。

言語の選択がバリデーション精度を左右する。

英語のほうが学習データが多いはずなのに、なぜか日本語のほうが論理的なカウント処理に強かった。

トークナイザーの仕様の違いが影響しているのかもしれない。

「fix: Geminiモデル名をgemini-3.1-flash-lite-previewに修正」

モデル名も間違えていて404エラーになっていた。

まだこの画像生成パイプラインは完全に安定していない。

手動で直す羽目になり、結局1時間無駄にした。

今週の数字

今週の開発ハイライト
今週の開発ハイライト

| 指標 | 今週の実績 | 比較対象 |

|------|-----------|---------|

| コミット数 | 17件 | 先週は42件。数は減ったが中身が重い |

| API費用 | $12.4 | 外注なら数十万円。僕はコーヒー3杯分 |

| 開発時間 | 約4時間 | 企業なら2-3週間。僕は半日 |

| タイトルHIT比 | 28.2 | 3週間前は26.0。微増に見えて別次元 |

1,663人の配信対象に対し、1,000件制限で運用していた。

実質39%の機会損失だ。

気付かずに運用を続けていたらと思うとゾッとする。

開発工数については、AIの活用で圧縮された。

本来2週間かかるリファクタリングを数時間で完了している。

一般的に人件費換算で約30〜50万円のコスト削減と言われている。

しんたろーしんたろー:
HIT比26.0から28.2まで上げるのに3週間かかった。数字で見るとたった2.2だけど、生成されるタイトルの質が明らかに変わった。プロンプトが証拠になる開発、思ったより好きだ。

「fix: タイトル文字数カウントを簡易版から正式なX weighted計算に統一」

「feat: タイトルv3プロンプトを大幅改良 — HIT比26.0/28.2まで到達」

文字数カウントの計算方法も直した。

Xは絵文字などで重みが変わるから、簡易計算だとズレるのだ。

ASCIIは1、それ以外は2という簡易版を捨てた。

CJK範囲判定や絵文字、ZWJ対応の正式な計算に置き換えた。

精度を上げたら、また別のところが崩れた。

ネタ情報からスペック数字を除去してAIに渡すようにした。

大げさなワードや自己啓発ワードを禁止リストに追加した。

広告臭スコアが2.7から10.0になり、完全解消された。

共感スコアも5.0から7.9に上がった。

開発の裏側FAQ

Q: メルマガのバウンス率チェックは具体的にどうやって実装している?

直前24時間の送信ログを集計し、エラー数を総送信数で割って算出している。

閾値を超えた場合は、Inngestの定期実行ジョブ内で強制的にエラーを吐かせて処理を中断させる。

これにより、AWS SESのアカウント停止リスクを未然に防いでいる。

Q: Server Actionの二重フォールバックはパフォーマンスに悪影響はないのか?

APIの過剰な呼び出しを招くため、本来は避けるべき泥臭い実装だ。

しかし、1人開発では「ユーザー画面が真っ白になる」ことの方が致命傷になる。

パフォーマンスの低下を受け入れてでも、確実にデータを取得する生存戦略を選んだ。

Q: Claude Codeにアーキテクチャを書き換えられた際の差分確認はどうした?

git diffで変更されたファイル数と行数だけを見て、あとは実際にブラウザで動かして確認した。

コードを1行ずつ読むのは諦め、ネットワークタブで余計なリクエストが飛んでいないかだけをチェックする。

AIのコードを人間がレビューするのではなく、ブラックボックスとしてテストする運用に切り替えた。

まとめ

動いているように見えて、裏では静かにしんどい状態だった1週間だった。

泥臭い検証の末に、少しずつプロダクトは前に進んでいる。

👉 ThreadPostでSNS運用を自動化する

ThreadPost — SNS投稿をAIが自動化

この記事が参考になったら、ThreadPostを試してみませんか?投稿作成・画像生成・スケジュール管理まで、AIがサポートします。

無料で始める

この記事をシェア

XはてブLINE
しんたろー

ThreadPost開発者・個人開発エンジニア

AI × SaaS個人開発者。Cursor / Claude Code を使った効率的開発、SNS自動化について実体験から発信。

人気の記事