
Cloud Run上でNode.jsアプリを動かし、パスワード再設定や問い合わせ通知、診断結果などをメールで送信したいケースは少なくありません。ところが、実装したはずなのにメールが届かないことがあります。
今回、実際に開発していた「AI・DX 業務改善 自動診断」でも、管理者パスワード再設定画面からメール送信を試したところ、画面上には「メールを送信しました」と表示されるのに、メールがまったく届かないという問題が発生しました。
最初はSMTP設定やパスワードを疑いました。しかし調べていくと、原因はもっと手前にありました。当初の /api/admin/password-reset は、成功メッセージを画面へ返しているだけで、実際のNodemailerによるメール送信処理そのものが実装されていなかったのです。
画面に「成功」と表示されたからといって、メール送信が成功したとは限りません。
メール送信には、「画面 → API → メール生成・送信処理 → SMTP → 受信サーバー・受信箱」という複数の工程があります。画面上の成功表示は、そのうち最初の一部が正常に進んだことしか証明していません。
この記事では、Cloud Run上のNode.jsアプリからメールが届かないときに、どの順番で原因を切り分ければよいのかを、実際に発生したトラブルをもとに解説します。Nodemailerの sendMail() や verify()、SMTP設定、Cloud Runの環境変数、Secret Manager、リビジョンの再デプロイまで、確認すべきポイントを順番に整理します。
Cloud Runで「メールを送信しました」と出ても届くとは限らない
画面表示が証明しているのは最初の工程だけ
メール送信トラブルで最初に見直したいのは、画面表示を証拠にしないことです。Webアプリでは、ボタンを押したあとにAPIが正常なレスポンスを返すと、フロント側で「メールを送信しました」と表示する実装がよくあります。
しかし、そのメッセージは必ずしも「SMTPサーバーへの接続に成功した」「メールが実際に送信された」「相手のメールサーバーまで到達した」「受信箱へ届いた」ことを意味しません。今回の実例でも、UI上は正常終了していましたが、そもそもAPIに実際のメール送信処理がありませんでした。
メール送信を一つの処理だと考えると、原因を見つけにくくなります。実際には複数の工程があります。
- 利用者が画面で送信ボタンを押す
- フロント側からAPIを呼ぶ
- APIがメール本文や宛先を生成する
- NodemailerがSMTP接続を行う
- SMTPサーバーがメールを送信する
- 受信側メールサーバーが受け取る
- 利用者の受信箱または迷惑メールフォルダに届く
つまり、「ボタンを押したら成功表示が出た」という事実だけでは、1〜2のあたりまでしか分かりません。その先の処理が本当に実行されたかは別途確認する必要があります。
メール送信は5つの工程で切り分ける
実務では「画面 → API → メール生成・送信処理 → SMTP → 受信サーバー・受信箱」の5段階で考えると、原因を特定しやすくなります。一度にすべての設定を変更するのではなく、「どこまでは正常か」「どこから先が動いていないか」を一つずつ切り分けます。

Cloud Runからメールが送れないときの確認順序
1.画面からAPIが本当に呼ばれているか
まず確認するのは、フロント画面です。送信ボタンを押したとき、本当にサーバー側のAPIへリクエストを送っているでしょうか。注意したいのは、画面上で成功メッセージを出す処理と、API呼び出しが別になっているケースです。
今回のトラブルのように、APIは200を返すもののメール送信処理は実行していないケースもあります。まずは「成功表示が出たか」ではなく「サーバー側のメール送信APIが本当に動いたか」を確認します。
2.APIがメール送信関数をawaitしているか
次に確認するのはサーバー側です。APIエンドポイントの中で、メール送信関数が実際に呼ばれている必要があります。関数を作成しているだけではメールは送られません。また、非同期処理であるにもかかわらず適切に await していない場合、処理完了前にAPIレスポンスだけ返してしまう可能性があります。
確認するときは、メール送信関数が存在するか、APIから実際に呼ばれているか、awaitされているか、エラーが握りつぶされていないかを見ます。
3.Nodemailerで実際の送信処理が実装されているか
Node.jsでSMTPメールを送る際によく使われるのがNodemailerです。ここでも、Nodemailerをインストールしたことと、メール送信処理が実装されていることは別です。SMTP設定を使ってtransportを作成しただけではメールは送信されません。実際には sendMail() を呼び出す必要があります。
4.SMTPへの接続・認証が成功するか
Nodemailerのコードが存在していても、SMTPサーバーへ接続できなければメールは送れません。SMTP_HOST、SMTP_PORT、SMTP_SECURE、SMTP_USER、SMTP_PASSを確認します。
さらに、Nodemailerの verify() を使うと、実際のメール送信とは切り離して、SMTPサーバーへの接続・認証が成功するか確認できます。
5.実際の受信箱まで届いているか
最後は必ず、実際のメールアドレスへ送信します。ログ上でエラーがないから成功、sendMail() が正常終了したから成功、ではありません。自分のメールアドレスへテストメールを送り、受信箱、迷惑メール、プロモーションなどの振り分け先まで確認します。
利用者から見える場所へ届いて初めて、メール送信は成功です。
Nodemailerではtransportを作るだけではメールは送信されない
Nodemailerを使うときに理解しておきたいのが、「transportの作成」と「メールの送信」は別処理だということです。
transportは「どの方法で送るか」の設定
代表的なイメージは次のようになります。
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});この処理で行っているのは、「どのSMTPサーバーへ接続するか」「何番ポートを使うか」「どのユーザーで認証するか」といった設定です。まだメールそのものは送信していません。
実際の送信はsendMail()を呼ぶ
await transporter.sendMail({
from: process.env.MAIL_FROM,
to: email,
subject: 'パスワード再設定',
text: '...',
});ここまで実行されて初めて、NodemailerがSMTPサーバーへメール送信を依頼します。AIにコードを書かせている場合も、「メール送信機能を作って」と依頼しただけで安心せず、実際に sendMail() まで呼んでいるかを確認してください。
verify()でSMTP接続だけを先に確認する
await transporter.verify();この処理で、SMTPサーバーへ接続できるか、認証情報が通るかを確認できます。メール本文の作成や宛先の問題と、SMTPそのものの接続問題を分離できるため、トラブルの切り分けが速くなります。
Cloud Runで確認するメール送信の環境変数
Cloud Run上でメールを送信するときは、コードのほかに環境変数も重要です。代表的な確認項目を整理すると次のとおりです。
| 環境変数 | 役割 | 主な確認ポイント |
|---|---|---|
| SMTP_HOST | SMTPサーバー | メール事業者指定値と一致しているか |
| SMTP_PORT | SMTPポート | SMTP_SECUREとの組み合わせ |
| SMTP_SECURE | 暗号化設定 | 使用ポートとの整合性 |
| SMTP_USER | SMTP認証ユーザー | 正しい送信アカウントか |
| SMTP_PASS | SMTPパスワード | Secret Manager参照にする |
| MAIL_FROM | 差出人 | From形式が正しいか |
| MAIL_REPLY_TO | 返信先 | コード側と変数名が一致しているか |
| MAIL_SIMULATION | 実送信制御 | 本番で実送信しない設定になっていないか |
SMTP_HOST
利用するSMTPサーバーのホスト名です。契約しているメールサービスやサーバーの指定値を使います。一文字でも違えば接続できません。
SMTP_PORT
SMTP接続で利用するポート番号です。代表的なものとして465などがありますが、利用するSMTPサービスの指定内容に合わせます。重要なのはSMTP_SECUREとの組み合わせです。
SMTP_SECURE
TLSなどを使うかどうかの設定です。465の場合は通常trueとして扱われることがありますが、実際には利用しているSMTPサービスの設定内容を優先してください。
SMTP_USER
SMTP認証で使用するユーザーです。メールアドレスがそのままユーザー名になるサービスもあります。送信元メールアドレスとSMTP_USERが必ず同じとは限らないため、サービス側の仕様を確認します。
SMTP_PASS
SMTP認証パスワードです。この値は特に重要です。ソースコードやGitHubへ直接書くべきではありません。Secret Managerに保存した値をCloud Runから参照する構成にすると、秘密情報をコードから分離できます。

MAIL_FROM
メール受信者から見える差出人です。SMTPサービス側の制限と合っていない場合は送信できないこともあるため、確認が必要です。
MAIL_REPLY_TO
利用者がメールに返信したときの返信先です。今回、実際に問題候補として見つかったのが環境変数名の不一致でした。
MAIL_SIMULATION
実際には送信せず、メール送信をシミュレーションする用途の設定です。本番環境でtrueになっていると、メール送信処理は正常に進んだように見えても実際には送らない状態になる可能性があります。
実際に起きたCloud Runメール送信の設定ミス
MAIL_REPLYTOとMAIL_REPLY_TOの名前が違っていた
環境変数は名前が完全一致していなければ読み込めません。例えばCloud Run側に MAIL_REPLYTO と設定しているのに、コード側が process.env.MAIL_REPLY_TO を参照していたら、値は取得できません。
人間が見ると「だいたい同じ名前」に見えますが、システムにとってはまったく別の変数です。Cloud Run側の変数名とコード側の参照名を一文字ずつ比較するのがおすすめです。
APP_URL・APP_UR・APP_BASE_URLの違いでリンク先が変わる
メールそのものは届いても、「メール本文内のリンクがおかしい」という問題が起きることもあります。APP_URL、APP_UR、APP_BASE_URLといった似た名称が複数存在すると、コードが意図しない値を参照する場合があります。
パスワード再設定メールの場合、メールが届くだけでは不十分です。リンク先が正しい本番URLかまで確認する必要があります。
Cloud Runで環境変数を変えただけでは本番へ反映されない
Cloud Runでは、環境変数を修正して設定画面を保存しただけで終わりではありません。設定変更後に新しいリビジョンをデプロイする必要があります。
重要なのは「設定変更 ≠ 本番反映」ということです。
この点はCloud Runに修正が反映されない原因|GitHub・Cloud Build・Revisionを確認する順番でも詳しく解説しています。
最新リビジョンへ100%トラフィックが流れているか確認する
新しいリビジョンが作成されても、そのリビジョンへ本番トラフィックが流れていなければ意味がありません。
コードや環境変数を変更したあとにメールが届かない場合は、新しいリビジョンが作成されているか、最新リビジョンへ100%トラフィックが流れているか、そのリビジョンの作成時刻が変更後かを確認してください。
Secret ManagerのSMTP_PASSを変更したのに反映されない理由
Secretにはバージョンがある
Secret Managerでは、既存のSecretを単純に上書きするのではなく、新しいバージョンを追加して管理できます。古い値を残せるため、ロールバックしやすく、変更履歴も管理しやすくなります。
Cloud RunがどのSecretバージョンを参照しているか確認する
Secretを更新したのに古いパスワードのまま動いている場合は、Cloud Runがどのバージョンを参照しているかを確認します。例えばCloud RunがVersion 1を固定参照している状態で、Secret ManagerにVersion 2を追加しても、そのままではVersion 1を読み続ける可能性があります。
Secret参照変更後もCloud Runを再デプロイする
ここでも同じ原則があります。「Secretを変えた = 本番で新しい値を読んでいる」ではありません。Secretに新しいバージョンを追加し、Cloud Run側の参照を確認し、必要に応じて参照バージョンを変更したうえで、新しいリビジョンをデプロイします。
Secret Managerの使い方そのものはCloud RunでパスワードやAPIキーを安全に管理する|Secret Manager入門で詳しく整理しています。
症状別|Cloud Runのメール送信トラブル切り分け表
ここまでの内容を症状別にまとめると、次のようになります。
| 症状 | 最初に確認する場所 | 次に確認する場所 |
|---|---|---|
| 成功表示だがメールが届かない | APIに実際のsend処理があるか | Cloud Runログ・SMTP |
| SMTP接続に失敗する | HOST・PORT・SECURE | USER・PASS・SMTP側制限 |
| テストメールは届くが本番メールが届かない | 本番関数の宛先・From | 条件分岐・ADMIN_EMAILS |
| メール内のリンクが違う | APP_BASE_URL | Cloud Run環境変数・再デプロイ |
成功表示だがメールが届かない
この場合、SMTPより前を疑います。今回の実例のように、そもそもsend処理がなかった可能性があります。最初からSMTP_PASSを何度も変更するより、APIからメール送信関数を呼んでいるか、sendMail() が存在するか、ログに送信処理到達が残っているかを確認する方が先です。
SMTP接続に失敗する
verify() などでSMTP接続に失敗する場合は、SMTP_HOST、SMTP_PORT、SMTP_SECURE、SMTP_USER、SMTP_PASSの順で確認します。特にPORTとSECURE、USERとPASSの組み合わせを確認します。
テストメールは届くが本番メールが届かない
テストメールが届くなら、Cloud Runから外部SMTPへ接続でき、SMTP認証が通り、Nodemailerで送信できる可能性が高くなります。問題はSMTPよりも本番のメール送信関数側にある可能性が高いため、宛先、From、条件分岐、ADMIN_EMAILS、本番処理から sendMail() まで到達しているかを確認します。
メール内のリンクが違う
この場合、メール送信そのものは成功しています。問題はURL生成です。APP_BASE_URLなどの設定、新リビジョンのデプロイ、正しいリビジョンへ100%トラフィックが流れているかを確認します。
パスワード再設定メールでは「届いた後」まで確認する
メールが届くだけではパスワード再設定は完成しない
パスワード再設定では、メールを送る、利用者がリンクをクリックする、再設定画面を開く、新しいパスワードを入力する、パスワードを更新する、新しいパスワードでログインする、ところまでが一つの流れです。
メールだけ直しても、リンクをクリックするとトップページへ戻ってしまうなら、利用者から見れば機能は完成していません。
再設定URLが本番ドメインか確認する
開発中はローカル環境や仮URLを使うことがあります。その値がCloud Runの本番環境に残っていると、「メールは届くがリンク先が開発環境」ということが起きます。必ずメール本文を実際に開き、リンクのURLを目視確認してください。
リンクから実際に新パスワードへ変更できるか確認する
パスワード再設定では、30分有効の署名付きトークン、purpose=password_reset、passwordVersion、jti、使用済みトークンの1回限りの利用、再設定後の旧リンク・旧パスワード無効化など、安全設計も重要です。
記事の中心はメール送信ですが、パスワード再設定機能では「届いた」だけで完成と判断しないことが重要です。
Cloud Runメール送信の本番テストチェックリスト
Cloud Run上でメール送信機能を実装したら、最後に本番環境で確認します。コードやログだけではなく、実際の利用者と同じ操作をするのがポイントです。
- NodemailerのSMTP疎通チェックが成功する
- 自分のメールアドレスへテストメールを送信する
- 自分の受信箱に実際に届く
- 迷惑メールフォルダも確認する
- 本番の診断結果メールや通知メールが届く
- パスワード再設定メールが届く
- メール内URLが本番ドメインになっている
- メール内リンクから正しい再設定画面が開く
- 新しいパスワードへ変更できる
- 新しいパスワードでログインできる
- 古いパスワードではログインできない
- 同じ再設定リンクを再利用できない
- Cloud Run再デプロイ後も正常に動作する
特に忘れてはいけないのが、自分のメールアドレスへ実際に送信することです。受信箱だけでなく、迷惑メールフォルダまで確認してください。
Cloud Runのメール送信で覚えておきたい3つの原則
1.画面の成功表示を証拠にしない
「送信しました」と表示されても、本当にメール送信処理が動いたとは限りません。今回の実例では、成功レスポンスを返すだけで送信処理自体がありませんでした。最初に確認するのは、画面ではなく処理の流れです。
2.問題は工程ごとに一つずつ切り分ける
メールが届かないからといって、コード、SMTP設定、Secret、Cloud Run、URL、メールサーバーを一度に変更すると、原因が分からなくなります。まずAPIから sendMail() まで到達しているか、次に verify() が成功するか、その次に実送信できるか、という順番で確認します。
3.設定変更後は本番リビジョンまで追いかける
Cloud Runでは「値を変えた」ことと「本番で新しい値を使っている」ことは別です。GitHub反映、Cloud Build成功、Cloud Run新リビジョン、100%トラフィック、本番URL実機確認までを一つの流れとして考えます。
自動デプロイの仕組みはCloud Buildトリガーとは?削除したCloud Runが勝手に復活した原因から学ぶ、AI Studioからの反映確認はAI Studioの修正がGitHubに反映されない?本番まで確認する5段階もあわせてご覧ください。
よくある質問
Cloud RunからSMTPを使ってメール送信できますか?
Node.jsアプリでNodemailerなどを利用し、SMTPサーバーへ接続してメールを送信する構成を取れます。ただし、Cloud Run上でコードが動いているだけではメールは送信されません。SMTP接続情報、認証情報、Nodemailerのtransport、sendMail()、Cloud Runの環境変数などを正しく設定する必要があります。
NodemailerでsendMail()が成功すればメール送信は成功ですか?
システム内部の送信処理が正常終了したことと、利用者の受信箱へ届いたことは同じではありません。最終的には実際のメールアドレスへテスト送信し、受信箱、迷惑メール、メール本文、リンク先まで確認してください。
SMTP_PASSをCloud Runの通常環境変数へ入れてもよいですか?
SMTP_PASSは秘密情報として扱い、Secret ManagerからCloud Runへ参照させる構成にすると、実パスワードをGitHubやソースコードから分離できます。
Cloud Runの環境変数を変更したのに反映されないのはなぜですか?
設定変更後に新しいリビジョンをデプロイしていない可能性があります。新しいリビジョンを作成し、そのリビジョンへ本番トラフィックが流れていることまで確認してください。
テストメールは届くのにパスワード再設定メールだけ届かない原因は?
SMTPそのものではなく、本番メール関数の可能性があります。宛先、From、条件分岐、ADMIN_EMAILS、本番処理から sendMail() が呼ばれているかを確認します。
まとめ|利用者へ届いて初めてメール送信は成功
Cloud Runからメールが届かないとき、最初に覚えておきたいのは、「メールを送信しました」という画面表示を成功の証拠にしないことです。今回の実例では、パスワード再設定画面に成功表示が出ていましたが、当初のAPIには実際のNodemailer送信処理そのものがありませんでした。
メール送信は「画面 → API → メール生成・送信処理 → SMTP → 受信サーバー・受信箱」という複数工程で動いています。そのため、問題が起きたら一つずつ切り分けます。
Nodemailerではtransportを作るだけで終わらず、実際に sendMail() が呼ばれているか確認します。SMTP接続については verify() で切り分けます。Cloud RunではSMTP_HOST、SMTP_PORT、SMTP_SECURE、SMTP_USER、SMTP_PASS、MAIL_FROM、MAIL_REPLY_TO、MAIL_SIMULATIONなどを確認します。
SMTP_PASSはSecret Managerから参照させ、Secretや環境変数を変更した場合は、新しいCloud Runリビジョンへ反映させます。そして最後は必ず、自分のメールアドレスへテストメールを送ります。受信箱だけでなく、迷惑メールまで確認してください。パスワード再設定メールなら、そのリンクをクリックし、実際に新しいパスワードへ変更できるところまで確認します。
システム内部の成功と、利用者が感じる成功は違います。利用者へ届いて初めて、メール送信は成功です。
あわせて、Cloud Runで保存したデータが消える理由|ローカル保存ではなく永続DBが必要、Google AI Studioでアプリ開発|何が作れる?業務アプリの実例と本番公開まで解説、Claude Codeでアプリ開発|AI Studio・GitHubとの役割分担と安全な修正方法、AIにアプリ開発を依頼するときの伝え方|完璧なプロンプトより重要な7項目もご覧ください。
Cloud Runの本番運用でお困りの方へ
AIを使えば、非エンジニアでも業務アプリの試作や開発を進めやすくなりました。一方、本番運用になると、メール送信、認証、Secret管理、データ保存、Cloud Runへのデプロイ、外部サービスとの連携など、複数の仕組みが関係します。
「AIでアプリの形までは作れたが、本番環境でどこが原因か分からない」「メールや認証など一部の機能だけ正常に動かない」という場合は、コードだけではなく、Cloud RunやSecret Managerを含めた全体構成から切り分けることが重要です。
相談前には、現在起きている症状、使用している開発環境、Cloud Runまでのデプロイ方法、エラーが発生する操作、Cloud Runログに表示されているエラー内容を整理しておくとスムーズです。SMTPパスワードやAPIキーなどの秘密情報そのものを送る必要はありません。
