40
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コードミスで自社メールサーバーを受信不能にした話

Posted at

コードミスで自社メールサーバーを受信不能にした話

― たった4行を書かなかったせいで会社のメールが全滅した ―

この記事は
本番環境などでやらかしちゃった人 Advent Calendar 2025
9日目 の記事です。

今回は、
「4行の if 文を入れ忘れた結果、自社で契約しているメールサーバーを物理的に爆撃した話」
を供養します。

きっかけ

「メールアドレスなしでもアカウント登録できるようにしたい」

もともと本サービスでは、初回登録時に以下の情報が必須でした。

  • メールアドレス
  • ユーザー名
  • 名前
  • 初回ログインパスワード(6桁)

しかし、公的機関向けに提供する都合上、

  • メールアドレスが 個人情報として取得しづらい
  • 組織メール(特に高校など)が ほぼ運用されていない

という現実的な問題に直面しました。

大学生なら機構メールは使いますが、高校以下になると
「アカウントはあるけど誰も見ていない」
という状況が本当に多い。

つまり
「メールアドレスを登録必須にするとサービスが使えない」

という結論になり、
「それならメールなしで登録できるようにしよう」
と即決しました。

実装した仕組み(ここまでは平和)

実装自体はとても単純です。

if (email.endsWith('@no-address.example.com')) {
    mail.certification(true);
}

このドメインを指定している場合は
「強制的にメール認証済み扱いにする」
という実装です。

ユーザー名の重複を防ぐため、
登録時には以下のようなダミーメールアドレスを自動生成していました。

g001_20250001@no-address.example.com
  • g001_20250001 → ユーザー名
  • @no-address.example.com → 自社ドメイン

このドメインは Cloudflare Email Workers を使って、
さくらインターネットのメールサーバーに転送していました。

余談:さくらのメールは受信専用なら最強

メールボックスだけなら年間 2,000円ちょっと で使えるので、
「とりあえず受信用メールが欲しい」用途には本当に便利です。

※なぜかメールが弾かれやすいので、送信には向きません。

認証フローの問題点(ここが伏線)

本サービスは、

  1. 認証URLをメールで送信
  2. そのURLにアクセス
  3. パスワード等をユーザーが設定

という一般的なフローでした。

しかし今回のケースでは:

  • 送信先は ユーザー本人が見ないダミーメール
  • 認証URLは DBを直接見て取得する想定

だったので、
「メールは送られなくていい」という前提になっていました。

やらかし発生

前準備が整い、
アカウント発行処理を一括で実行しました。

すると――

さくらのメールサーバーが容量制限で死亡
社内の全メールが受信不能に

原因は単純でした。

先ほどのコードは
「認証を強制的に true にする」処理のみで、

👉 「認証メールを送らない」処理を書いていなかった

つまり、

  • 認証済み扱い
  • でも 認証メールは全件送信

という地獄の挙動になっていました。


追い打ち:現場猫ムーブ

さらに最悪だったのがこれ。

  • 当該パッチは インターン生が実装
  • コードレビューなし
  • 「まあ大丈夫でしょ」という 現場猫判断
  • そのまま 本番実行

結果、発行されたアカウント数は……

2,147,483,647 件

(※ 当時のコードが残っておらず、なぜこの数字になったのかは未だに不明です)

メールサーバーは、

  • 大量送信
  • 大量受信
  • 容量上限に達する
  • 会社全体のメールが 物理的に死亡

という完璧なコンボを決めました。

なお、なぜか アクセス制限やスロットリングは一切かからず
「容量制限に当たるまで送られ続ける」 という地獄仕様でした。

どうすれば防げたのか

答えは、たったこれだけでした。

if (email.endsWith('@no-address.example.com')) {
    certification = true;
}
else {
    mail.send(certification_template(code));
}

たった 3 行の else 分岐。

これがあれば、

  • ダミーメール → 認証のみ
  • 通常メール → 認証メール送信

という正しい世界線に分岐できていました。

結論(血の教訓)

今回の事故から得た教訓はこれです。

  • 仕様が変わる時は「既存フロー前提」で雑に継ぎ足さない
  • 想定外の入力(今回で言うダミーメール)には 必ず分岐を書く
  • どんなに急いでいてもコードレビューは省略しない

そして何より――

「送信系の処理は最悪“爆撃”になる」

ということを、身をもって学びました。

おわりに

なお、この事故は夜中に実行して、夜中に解消できたので開発陣営以外は知る由もありませんでした。
よって、誰にも怒られることなく闇に葬られました。

Advent Calendar らしく、
どこかの誰かが 同じ地雷を踏まないこと を願って、供養として公開します。

40
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?