【トラブル解決】Railwayからメールが送れない…原因は「SMTPポートの遮断」でした

代理店向けのWebシステムに、問い合わせ通知メールの機能を実装しました。プログラムはすんなり完成。あとは契約済みのレンタルサーバーのメールアカウントに繋ぐだけ……のはずでした。
「あれ? 送信できない……」
設定は何度見直しても合っている。それなのに、テスト送信を押すと必ずタイムアウトする。ここから数時間の格闘が始まりました。結論から言うと原因はコードでも設定でもなく、PaaS側がSMTP通信そのものを遮断していたというものでした。同じ構成でハマる人は多そうなので、切り分けの過程ごと残しておきます。
やりたかったこと
実装したのは、よくある通知メールの機能です。
- 問い合わせが入ったら担当代理店へ通知(個人情報は本文に含めない)
- 同じ問い合わせを本部にも通知
- 本部からのお知らせを全代理店へ一斉送信
- パスワード再設定リンクの送信
メールアドレスはすでに持っているので、そのSMTPサーバーに繋ぐだけ。10分で終わる作業だと思っていました。
問題1:設定は正しいのにタイムアウトする
ホスト・ポート・ユーザー・パスワードを環境変数に設定してテスト送信。返ってきたのは timed out だけ。
まず疑うのは設定ミスです。でも手元のPCから同じサーバーの587番ポートに繋ぐと、あっさり応答が返ってきました。
port 587: 接続OK 220 sv****.example.jp ESMTP Postfix
設定は合っている。アプリのサーバーからだけ届かない。 ここが分かれ道でした。
問題2:もっともらしい仮説にハマる
アプリのコンテナから外向きのIPアドレスを調べたところ、アメリカのIPでした。
ここで思いつきます。「国内のレンタルサーバーは、海外からの接続を初期設定でブロックしていることがある。国外IPアクセス制限に弾かれているのでは?」
筋は通っています。実際そういう仕様のサーバーは多い。危うくレンタルサーバー側の設定を触り始めるところでした。
でも、これは外れでした。
決め手になったのは、まったく関係ない別のSMTPサーバー(Gmail)に繋いでみるという検証でした。
smtp.gmail.com への接続
port 587: Network is unreachable
port 465: Network is unreachable
port 25 : Network is unreachable
宛先を変えても同じ。しかもエラーが timed out ではなく Network is unreachable、つまりネットワークの経路そのものが存在しない状態でした。
これで確定です。PaaS側が、宛先を問わずSMTPポート(25 / 465 / 587)を遮断していました。 迷惑メールの踏み台として悪用されるのを防ぐため、多くのPaaSが標準でこの制限をかけています。レンタルサーバーは無実でした。
もしあのまま国外IP制限を疑い続けていたら、何時間も溶かしていたはずです。「同種の別サービスで再現するか試す」のは、原因がこちら側か向こう側かを一発で切り分けられる手段でした。仮説がもっともらしいほど、この検証を挟む価値があります。
解決策を検討する
SMTPが使えない以上、別の経路が必要です。ここで重要なのは、HTTPS通信は普通に通っていたという点。塞がれているのはメール専用のポートだけでした。
案A:メール配信APIを使う
ResendやSendGridなど、HTTPS経由でメールを送れるサービスを使う方法。王道です。ただ無料枠には日次上限があります。
| サービス | 無料枠 |
|---|---|
| Resend | 1日100通(月3,000通) |
| SendGrid | 1日100通 |
| Brevo | 1日300通 |
ネックだったのは、開発中のテスト送信でもこの枠を消費すること。動作確認を繰り返すと1日100通はあっという間です。有料プランは月20ドル前後と決して高くありませんが、複数システムへ横展開する前提だと悩ましいところでした。
案B:自前のレンタルサーバーを中継役にする(採用)
ここで気づきました。すでに契約しているレンタルサーバーが遊んでいる。 メール用にしか使っていませんでしたが、PHPが動くWeb領域も付いてきます。
【これまで】
アプリ ──SMTP──→ [PaaSが遮断] ──→ メールサーバー
【変更後】
アプリ ──HTTPS──→ レンタルサーバー上のPHP ──→ メール送信
(普通のWeb通信なので通る)
レンタルサーバーに50行ほどのPHPスクリプトを1つ置き、アプリからは「この宛先に、この件名で、この本文を送って」とHTTPSで依頼するだけ。実際の送信はレンタルサーバーが行うので、PaaSの制限を回避できます。
そして送信上限が桁違いでした。
| 送信経路 | 1日あたりの上限 |
|---|---|
| メール配信サービス(無料枠) | 100〜300通 |
| 国内レンタルサーバー | 10,000〜15,000通 |
すでに払っている費用の範囲内で、無料枠の数十倍。追加費用ゼロで解決しました。
問題3:中継スクリプトは踏み台になり得る
この構成には落とし穴があります。「URLを叩けば誰でもメールを送れる仕組み」をインターネット上に公開することになるという点です。対策なしでは、迷惑メールの発射台を自分で設置するようなものです。
そこで以下を実装しました。
- 共有トークンによる認証:ヘッダに秘密の文字列を入れ、一致しないリクエストは拒否。比較にはタイミング攻撃に強い関数を使用
- 差出人アドレスをスクリプト側で固定:依頼する側から差出人を指定できない設計にする
- 1時間あたりの送信数制限:トークンが漏れた場合の被害を抑える保険
- ヘッダインジェクション対策:宛先や件名に改行が混入したリクエストを拒否
特に2つ目が効きます。「誰に送るか」は依頼側が決めるが、「誰として送るか」はサーバー側で固定する。 これだけで、万一トークンが漏れても他人になりすましたメールは送れません。悪用時の被害が大きく変わります。
問題4:SPFの不一致という罠
途中で「別のレンタルサーバーから送ってもいいのでは」という案も出ましたが、これには注意が必要でした。
送信元アドレスのドメインにはSPFレコードという「このサーバーからの送信を許可する」というDNS設定があります。A社のサーバーのアドレスを差出人にして、B社のサーバーから送ると、この設定と食い違って迷惑メール判定されやすくなります。
結局、メールアドレスを持っているサーバーと、中継スクリプトを置くサーバーを一致させるのが最もシンプルでした。DNSを触る必要もなく、テスト送信は迷惑メールフォルダではなく受信トレイに届きました。
おまけ:CLIは環境変数を平文で表示する
作業中にヒヤリとした話も残しておきます。
PaaSの管理画面では、環境変数の値は ******* と伏せられています。ところがコマンドラインツールで一覧を取得すると、値がそのまま出力されます。
「パスワードが設定されたか確認したい」だけのつもりが、APIキーや署名鍵まで含めて全部が画面に出てしまいました。
確認したいのは「設定されているか」であって「値が何か」ではないはずです。
# 悪い例:全部の値が表示される
$ cli variables
# 良い例:存在するかどうかだけ分かる
$ cli variables | grep -c "SMTP_PASSWORD"
1
秘密情報は、一度でもログや画面に出たら「漏れた前提」で作り直す。これが原則です。実際この件では、該当するキーをすべて再発行しました。地味ですが、事故になる前に気づけたのは幸いでした。
まとめ
- PaaSはSMTPポートを塞いでいることが多い。設定を疑う前に、別のSMTPサーバーで再現するか試すと切り分けが早い
- HTTPSは通るので、HTTPS経由でメールを送る手段を探せばよい
- メール配信サービスは手軽だが、無料枠の日次上限は開発中のテストで埋まりやすい
- すでに契約しているレンタルサーバーを中継役にすると、追加費用ゼロで上限も桁違いに大きい
- 中継スクリプトには認証・差出人固定・レート制限を必ず実装する
- 差出人ドメインのSPFと、実際に送信するサーバーを一致させる
- 環境変数の確認は、値ではなく存在の有無だけを見る
「使っていないサーバーが手元にある」という状況は意外とよくあります。新しいサービスを契約する前に、手持ちの資産で解決できないか一度考えてみると、コストも依存先も減らせるかもしれません。
公開日 2026/09/04