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

「いや長すぎだろ」。Claude Codeで1人開発してステップメールを作ったら、バグの連鎖で32日間が完全に溶けた。

「いや長すぎだろ」。Claude Codeで1人開発してステップメールを作ったら、バグの連鎖で32日間が完全に溶けた。
しんたろーしんたろー
13分で読めます
この記事の内容(目次)
※この記事は、Claude Codeで1人開発しているSNS運用SaaS「ThreadPost」の開発日記です。

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

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

無料で始める

核心回答:「動いた」と「動いてる」の絶望的なギャップ

「fix: Use RPC function for Vault access」「fix: email_send_logsのカラム名をcreated_at→sent_atに修正」。今日のコミット履歴を眺めたら、fixだらけだった。

「動いた」と「動いてる」のギャップを埋める可視化
「動いた」と「動いてる」のギャップを埋める可視化

43コミット。新機能は2件。バグ修正は3件。 残りは何だったかというと、「動いてると思ってたのに動いてなかった」を直した跡だ。

ローカル環境では完璧に動いていた。テストコードも全てグリーンだ。

でも本番環境にデプロイした瞬間に沈黙した。

汎用メールキャンペーン機能を作った。ステップメール7通、A/Bテスト、バウンス管理、自動停止ロジック。全部入りだ。

一番キツかったのは「動いた」と「動いてる」が違うと気づいた瞬間だ。

コードは走る。テストも通る。でも画面には「送信数: 0」と出ている。

DBには普通にデータがある。お前それ見えてないだけだろ、という状態が何度も続いた。

SaaS開発において、ローカルで動くことと本番でユーザーに価値を届けることの間には巨大な壁がある。

バグの連鎖を断ち切るために、泥臭い可視化の作業に時間を溶かした。

管理画面を作るのは、本編の機能を作るのと同じくらい時間がかかる。

でもそれがないと、システムが今どうなっているのか誰にも分からない。

ブラックボックスの中で何かが動いている状態は、開発者にとって恐怖でしかない。

完璧なバリデーションがリードを破壊する

最初は軽く考えていた。メールアドレスのバリデーションなんて、正規表現を一個書けば終わる。

甘かった。

「feat: メールアドレスバリデーションを厳格化」を実装した。RFC 5322準拠の正規表現を入れた。

一般的に、メールアドレスの仕様は歴史的経緯でカオスになっている。完全準拠の正規表現を書こうとすると数千文字になると言われている。

長さチェック。ローカルパートのルール。ドメインのTLD検証。完璧に見えた。

実行してみたら、大量の有効なアドレスが無効として除外された。

ここで終わりだと思った。終わりじゃなかった。

「fix: 全角@を半角に自動変換してメールアドレスを救済」。このコミットを打つことになった。

全角の「@」でメールアドレスを登録してきたユーザーがいた。 スマホのキーボード設定によっては全角がデフォルトになっている。

バリデーション強化で全員弾かれていた。完璧なバリデーションを作ったつもりが、ただのリード破壊マシーンになっていた。

変換してから検証する。順番を逆にした。これで一部のアドレスが復活した。

でもまだ終わらない。

しんたろーしんたろー:
「バリデーション一個書けばいい」から始まって、結局全角半角の変換処理まで書くハメになった。ユーザーの入力はカオスだ。全角の@なんて誰が使うんだよと思ったけど、普通にいた。

AWS SESのアカウント停止リスクとの戦い

次は「feat: メール送信前にMXレコード検証を追加」だ。

正規表現を通過しても、そのドメインが実在しないケースがある。存在しないドメインに送り続けると、バウンス率が跳ね上がる。

AWS SESはバウンス率が一定を超えるとアカウントを停止する。

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

アカウントが停止されると、既存ユーザーへのメールも一切送れなくなる。SaaSにとって致命傷だ。

メール配信インフラを自分で持つということは、このリスクと常に隣り合わせということだ。

MXレコード検証を実装した。ドメイン単位で24時間キャッシュ。存在しないドメインへの送信を事前にブロックする。

「feat: インポート時の検証ロジックを改善」。インポート時は正規表現+ブロックリスト、送信直前はMX検証という二段構えにした。

無効メールは「status='invalid'」でDBに記録する。トレーサビリティを確保した。

「feat: メールアドレス自動修正パターンシステムを実装」。さらに進んで、よくあるタイポパターンをDBで管理するテーブルを作った。

全角@、重複@@、ドット抜け。修正した場合は「metadata」に元のアドレスを記録する。

バグ1個直すと次が出てくる。それがずっと続いた。

しんたろーしんたろー:
MXレコード検証のAPI叩きすぎてレートリミットに引っかかった。キャッシュ実装するの忘れてた。外部APIに依存する処理は、とりあえずキャッシュ挟まないとすぐ怒られる。

データは「ある」のに、画面には「ない」

送信ログが取れているはずなのに、画面に出ない。

「fix: email_send_logsのカラム名をcreated_at→sent_atに修正」。これが最初のコミットだ。

深夜0時過ぎ。日次レポートで「本日送信: 0」と表示されていた。

DBを直接見たら、送信ログは普通にある。カラム名が違った。

「email_send_logs」テーブルには「created_at」が存在しない。「sent_at」を使う必要があった。

作成日時と送信日時は別物だ。キューに入った時間と実際に送られた時間はズレる。

たった一行の修正だ。でもこれを見つけるまでに時間がかかった。

次に踏んだのはRLSだ。

SupabaseのRLS(行レベルセキュリティ)は、ポリシー設定を間違えると「クエリが失敗する」のではなく「空の結果を返す」。

これが厄介だ。エラーが出ない。ただ静かに「データなし」と表示される。

サイレントフェイルだ。 開発者は「データがない」のか「権限がない」のかの切り分けで時間を溶かす。

送信ログが取れている。でも画面に出ない。DBには普通にある。

「データないです」って顔してるけど、お前それ見えてないだけだろ。

ポリシーを修正した。送信数、開封率、バウンス率が画面に出た。

「動いてます」を信じるのをやめた瞬間だ。目で確認できる状態にして初めて「動いてる」と呼ぶ。

「feat: 管理画面に開封率・クリック率を表示」。送信履歴ページに開封とクリックの統計カードを追加した。

詳細ログページに開封日時とクリック日時の列を追加した。数字が見えた。これで初めて「動いてる」と言える状態になった。

しんたろーしんたろー:
RLSのデバッグは本当にしんどい。エラーが出ない。ただ空っぽが返ってくる。「データがない」のか「見えてない」のかを切り分けるのに、DBを直接叩いて確認するしかない。開封率が画面に出た瞬間、ちょっと安心した。

ここまで読んだあなたに

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

無料で始める

「1件しかないはず」という性善説

「maybeSingle」クエリで1件だけ取得しようとした。

SupabaseのJavaScriptクライアントの仕様だ。「1件しか返ってこないはず」という前提で書いたコードだ。

DBには重複データがあった。クエリが「複数あります」とエラーを吐いた。 システムが全停止した。

「1件しかないはず」という開発者の性善説が、DBの現実にボコボコにされた。

データの重複がなぜ起きるか。ネットワークの瞬断でクライアントがリトライをかけると、DBには2件登録されることがある。

修正は単純だ。「maybeSingle」をやめて、複数返ってきても処理できるように直した。

でも「なぜ重複があるのか」を追うのに時間がかかった。インポート処理の差分チェックが甘かった。

バッチ処理の冪等性(べきとうせい)が担保されていなかった。 何度実行しても同じ結果になる設計が必要だった。

「feat: ZED自動インポートを全件取得+差分インポートに改善」。

毎日全件処理から、DBの最新データを基準に新規分のみを処理する方式に変えた。

DBには性善説が通じない。

今日の数字で振り返る

本日の開発スタッツ
本日の開発スタッツ

| 指標 | 今日 | 比較 |

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

| 総コミット | 43件 | チーム開発なら複数人で分担する量 |

| 新機能 | 2件 | バグ修正・改善が大半を占めた |

| バグ修正 | 3件 | 「動いた」から「動いてる」への道のり |

| 自動インポート改善 | 毎日6時・差分のみ | 手動インポートなら毎回数時間の作業 |

一番数字として面白いのは、手動インポートと自動インポートの差だ。

データは毎日更新される。改善前は全件を毎回全処理していた。

改善後はDBの最新データを基準に差分のみを処理する。 毎日6時に自動で動く。

手動でやっていたら毎回数時間かかる作業が、ゼロになった。

開発コストはかかった。でも運用コストはゼロに近づいた。

まだ動いてない、気になってる

バウンス率の監視ロジックを実装した。

AWS SES基準に準拠して、バウンス率10%超・苦情率0.5%超で自動停止する。

でもこれ、本番で一度も発動したことがない。 発動しないのが正解だ。

でも「本当に動くのか」は確認できていない。自動停止が必要な状況になってから初めてわかる。

それが少し気になっている。

あと、A/Bテストの勝者判定ロジックも実装した。最小サンプル50件、開封率ベースだ。

これも本番データが溜まらないと評価できない。仕組みは作った。機能するかどうかは、これから。

よく聞かれそうなこと

Q: 自前で「parseParagraph」を書いたのはなぜ?既存のHTMLエディタじゃダメだったの?

メール配信では特定のメーラーで崩れないことが最優先される。既存のリッチテキストエディタは過剰なタグを生成しがちで、GmailやOutlookで崩れるケースが出やすい。Markdownライクな独自記法をHTMLに変換するパーサーを自前で書いた方が、結果的に保守コストが下がる。

Q: バウンス率の監視ロジックはどうやってテストするの?

AWS SESのシミュレーターを使って意図的にバウンスを発生させる。本番のメールアドレスを汚さずに、バウンス率が跳ね上がった時の自動停止ロジックを検証できる。テスト環境で10%を超えた瞬間にキューが止まるか確認した。

Q: SupabaseのRLSデバッグで一番手っ取り早い方法は?

サービスロールキーを使ってクエリを叩き、データ自体の有無をまず確認する。データがあるならRLSの問題だと切り分けられる。その後はポリシーの「using」句を一つずつ外して、どの条件で弾かれているか特定していく。

まとめ

「動いた」と「動いてる」は別物だった。

43コミットかけて、ようやくそれが言える状態になった。

自動化ツールを作っているはずが、一番手作業に追われていたのは僕だった。

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

ThreadPost — SNS投稿をAIが自動化

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

無料で始める

この記事をシェア

XはてブLINE
しんたろー

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

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

人気の記事