Vercel × Xserver で SMTP できなかった話|Resend 移行で 554 5.7.1 を解決した 3 日間

目次を表示
個人事業主として自社サイトを運営していると、ある日突然、問い合わせフォームから連絡が届かなくなります。
私自身、Vercel にホスティングした Next.js サイトでこの問題に直面しました。原因を追っていくと、SMTP 認証エラー(EAUTH 535)から始まり、Xserver の構造的な制約(554 5.7.1 Client host rejected)にぶつかり、最終的に Resend という別の SMTP プロバイダーへ移行することで解決しました。
この記事では、3 日間の切り分け過程を時系列でそのまま共有します。Vercel や Netlify、Cloudflare Pages などのモダンホスティングを使いながら、Xserver やさくらなどの既存レンタルサーバーの SMTP を流用しようとして詰まっている方に、最短で答えにたどり着いてもらうための記録です。
結論(TL;DR)
- Vercel から Xserver SMTP は IP 拒否で構造的に動かない。Resend / SES / SendGrid などメール配信サービスを必ず挟む。
- Resend を nodemailer で使うときは SMTP_USER が固定文字列 "resend" のため envelope.from を FROM_EMAIL(認証済アドレス)で明示する。
- 環境変数の改行汚染は echo ではなく printf で防ぐ。疑わしい時は vercel env pull → cat -A で末尾 $ を確認。
問題が起きた環境はどんな構成だったのか?
まずはどんな構成で起きた問題かを共有します。
- フロント / API: Next.js 16(App Router)
- ホスティング: Vercel(Production / Edge Functions ではなく Node ランタイム)
- SMTP: Xserver の独自ドメインメール(noreply@oceans-base.com を発行)
- メール送信ライブラリ: nodemailer 7 系
- 送信先: 管理者通知(個人 Gmail)と、自動返信メール(送信者のアドレス)の 2 通
API ルート `/api/contact` で `nodemailer.createTransport` を作り、フォーム送信時に 2 通同時送信する、ごく普通の構成です。ローカルでは問題なく動いていました。
Phase 1:なぜ EAUTH 535 5.7.8 で認証が通らなかったのか?
本番環境で問い合わせフォームを送信すると、Vercel のランタイムログに以下のエラーが出続けました。
Error: Invalid login: 535 5.7.8 Error: authentication failed
code: 'EAUTH',
response: '535 5.7.8 Error: authentication failed',
responseCode: 535,
command: 'AUTH PLAIN'EAUTH 535 は SMTP 認証失敗を示す標準エラーです。原因の候補は、ユーザー名 / パスワードの誤り、認証方式の不一致、送信元 IP のブロックの 3 つです。
切り分けで詰まった点:Vercel ログが 80 文字で切れる
Vercel のランタイムログはデフォルトで 1 行が長すぎると省略されます。nodemailer のエラーオブジェクトには `command` や `response` といった重要情報があるのに、肝心の部分が見えませんでした。
そこで、API ルートに「デバッグヘッダゲート」を仕込みました。特定のヘッダ(`x-contact-debug: 1`)が付いたリクエスト時だけ、エラー詳細を JSON でレスポンス body に露出する仕組みです。本番に置きっぱなしでも安全な暫定デバッグ手段として機能します。
// 本番でも安全に使えるデバッグゲート
const debug = req.headers.get('x-contact-debug') === '1'
try {
await transporter.sendMail(/* ... */)
} catch (err: any) {
console.error('[contact] sendMail failed', err)
return NextResponse.json(
{
ok: false,
// デバッグヘッダ付きの時だけ詳細を返す
...(debug && {
debug: {
code: err.code,
command: err.command,
response: err.response,
responseCode: err.responseCode,
},
}),
},
{ status: 500 }
)
}これで `curl -H "x-contact-debug: 1" ...` で叩くと、ブラウザの Network タブから完全なエラー JSON が見られるようになりました。
真因:SMTP_PASS の typo(末尾改行)
結局、原因は単純なパスワードの打ち間違いでした。Xserver の SMTP パスワードは英数記号の長い文字列で、Vercel の環境変数フォームにブラウザで貼り付けた際、末尾に改行 `\n` が混入していたのです。
Vercel CLI で `vercel env ls` してもパスワードは伏せ字で表示されるため、目視確認できません。改行汚染を疑って `vercel env rm` してから再投入したところ、認証は通るようになりました。
再投入時は `echo` ではなく `printf` を使います。`echo` は末尾に改行を付けるため、これだけで `\n` が混入します。
# NG(末尾改行が混入する)
echo "$SMTP_PASS" | vercel env add SMTP_PASS production
# OK(末尾改行なし)
printf '%s' "$SMTP_PASS" | vercel env add SMTP_PASS production後ほど触れますが、Vercel 環境変数の改行汚染は、私のプロジェクトで合計 13 件見つかりました。`sitemap.xml` のビルド失敗や Sanity の 401 エラーなど、別の問題の根本原因にもなっていました。
Phase 2:なぜ Vercel + Xserver で SMTP は動かないのか?(554 5.7.1 Client host rejected の真因)
認証は通るようになったのに、今度は別のエラーが出るようになりました。
EnvelopeError: 554 5.7.1 <ec2-34-230-84-207.compute-1.amazonaws.com[34.230.84.207]>:
Client host rejected: Access denied
code: 'EENVELOPE',
command: 'RCPT TO'これは認証が通った後、宛先指定(RCPT TO)の段階で SMTP サーバーから拒否されている、という意味です。エラーメッセージに含まれる `ec2-...amazonaws.com` の文字列が決定的な手がかりでした。
真因:Xserver は AWS / Vercel の IP からの SMTP relay を一律拒否
Xserver のメールサーバーは、スパム対策として AWS、GCP、Azure などのクラウド IP レンジからの SMTP リレー要求を一律で拒否する設定になっています。Vercel の Node.js ランタイムは AWS Lambda 上で動くため、送信元 IP は AWS の動的 IP になります。Xserver から見ると、これは「クラウド経由のスパム送信元」と区別がつかないため、認証が通っていてもブロックされるのです。
これに気付くのに半日かかりました。「認証通ってるのになぜ送れない?」と何度もパスワードを再投入し、認証方式を切り替え、ポートを 587 と 465 で行き来しました。すべて無駄でした。
なぜ既存の PHP サイトでは動くのか
ここで疑問が出ます。同じドメイン(例えば `oceans-base.com`)の別サイトを Xserver 内の WordPress や独自 PHP で運用している人は、`mail()` 関数で問題なくメールを送れているはずです。なぜでしょうか。
答えは、PHP の `mail()` 関数が SMTP リレーではなく、Xserver サーバー内部の sendmail を直接呼んでいるからです。サーバー内部での送信なので IP チェックがそもそも発生しません。一方、Vercel 上の Node.js から SMTP リレーする場合は、外部から認証付きで接続するため、IP フィルタが容赦なく効きます。
Xserver の SMTP は「Xserver 内で動くアプリ向け」に設計されており、外部クラウドからの利用は想定外です。Xserver が悪いのではなく、レンタルサーバーの SMTP はもともとそういう設計です。
結論:Vercel × Xserver SMTP は構造的に成立しない
IP ベースの拒否なので、設定で回避できる問題ではありません。固定 IP のホワイトリスト登録は個人事業主向けプランでは現実的に不可能です。Vercel を使い続けるなら、別の SMTP プロバイダーへ切り替えるしかありません。
Phase 3:なぜ Resend を選んだのか?(SendGrid / SES との比較)
代替候補としては、SendGrid、Amazon SES、Mailgun、Postmark、Resend などがあります。最終的に Resend を選びました。
- 無料枠が月 3,000 通 / 日 100 通と、個人事業主の問い合わせフォーム用途に十分
- nodemailer 互換の SMTP インターフェイスを提供(既存コードを大きく書き換えずに済む)
- ドメイン認証(DKIM / SPF)の手順がドキュメント化されていて分かりやすい
- API ベースの送信もサポートしており、後から SDK へ移行する選択肢も残せる
- Vercel との親和性が高く、公式チュートリアルにも採用されている
SendGrid や Amazon SES でも同じことはできます。Resend が特別優れているわけではなく、「個人事業主が最短で動かす」観点で摩擦が少なかったのが選定理由です。
ドメイン認証で必要だった DNS レコード 3 件
Resend のダッシュボードでドメインを追加すると、3 種類の DNS レコードを表示してくれます。Xserver の DNS 設定画面(または使っている DNS プロバイダー)で、これらを追加します。
私の場合は `send.oceans-base.com` というサブドメインを Resend が指定してきたので、それに合わせて以下を追加しました。
# SPF(送信元として AmazonSES を許可)
send.oceans-base.com. TXT "v=spf1 include:amazonses.com ~all"
# DKIM(電子署名)
resend._domainkey.oceans-base.com. TXT "p=MIGfMA0GC..."
# MX(バウンス受信用)
send.oceans-base.com. MX 10 feedback-smtp.us-east-1.amazonses.comDNS の浸透には数分から数時間かかります。Resend ダッシュボードの「Verify」ボタンを定期的に押して、3 件すべてが Verified になるまで待ちます。
送信用アドレス noreply@oceans-base.com の発行
ドメイン認証が完了したら、`noreply@oceans-base.com` のような送信専用アドレスを Resend 側で設定します。実際にはメールアドレスを「作る」のではなく、認証済みドメインの任意のアドレスを From として使えるようになる、というイメージです。
個人事業主の問い合わせフォームでは、自動返信は送信専用が前提です。受信したい場合は別途 Reply-To ヘッダで個人 Gmail などを指定し、ユーザーの返信が直接届くように設計します。
Phase 4:Resend に変えても EENVELOPE が出るのはなぜか?
環境変数を Resend 用に書き換え、デプロイ。これで終わりだろうと思ったら、また `EENVELOPE` が出ました。
EnvelopeError: 550 The 'from' address must match a verified domain
code: 'EENVELOPE',
command: 'MAIL FROM'今度は MAIL FROM、つまり envelope-from の段階で拒否されています。Resend は envelope-from が認証済みドメインのアドレスでないと送信を拒否する仕様です。
真因:SMTP_USER に "resend" を入れる仕様が、nodemailer の自動 envelope と衝突する
Resend の SMTP 接続は、認証情報として以下を使います。
- SMTP_HOST: smtp.resend.com
- SMTP_PORT: 465(SSL)
- SMTP_USER: 固定文字列 "resend"(ユーザー名ではなくリテラル)
- SMTP_PASS: API キー(re_xxxxx の形式)
問題は、nodemailer が `from` フィールドからメールアドレス部分を抽出して envelope-from に自動設定しようとする、その挙動と相性が悪いことです。
もし `from: process.env.SMTP_USER` のように書いていると、envelope-from に `"resend"` という文字列が入ってしまい、当然これは有効なメールアドレスではないので Resend に拒否されます。
解決策:envelope を明示的に指定する
修正は単純で、`sendMail()` の呼び出し時に `envelope` を明示し、`from` には Resend で認証済みのアドレス(例:`noreply@oceans-base.com`)を入れます。
const fromStr = `"${process.env.FROM_NAME ?? 'OceansBase'}" <${
process.env.FROM_EMAIL ?? process.env.SMTP_USER
}>`
// envelope-from は実アドレス(FROM_EMAIL)を使用。
// Resend では SMTP_USER が固定文字列 "resend" のため、
// SMTP_USER を envelope に使うと「invalid sender address」で拒否される。
const envelopeFrom = process.env.FROM_EMAIL ?? process.env.SMTP_USER!
await transporter.sendMail({
from: fromStr,
to: process.env.NOTIFY_TO,
replyTo: email.trim(),
envelope: { from: envelopeFrom, to: process.env.NOTIFY_TO! },
subject: '...',
html: '...',
})`FROM_EMAIL` 環境変数に `noreply@oceans-base.com` を入れておけば、envelope-from もヘッダの From も同じ認証済みアドレスに揃います。
デプロイ後にフォームを送信したところ、Vercel ログに `status=201` と返ってきて、無事に管理者通知と自動返信の 2 通とも届きました。3 日間の切り分けがここでようやく終わりました。
SMTP 切り分け中に副次的に見つかった 2 つの構造問題
SMTP の問題を追いかける過程で、別の根深い問題が 2 つ見つかりました。同じ穴にはまっている方も多いと思うので共有します。
Vercel 環境変数の改行汚染(13 件)
Phase 1 の `SMTP_PASS` typo の調査中に、`vercel env pull .env.production.local` でローカルに環境変数を吐き出して `cat -A` で見たところ、複数の変数の値の末尾に `$` が付いていることに気付きました。これは LF 改行が混入している印です。
具体的に汚染されていたのは、`NEXT_PUBLIC_SANITY_PROJECT_ID`、`NEXT_PUBLIC_SITE_URL`、`SANITY_API_TOKEN` など 13 件。これが原因で `sitemap.xml` のビルドが「projectId can only contain a-z, 0-9 and dashes」で失敗し、Sanity への API 接続が 401 で弾かれていました。
対策は、すべての環境変数を `vercel env rm` で消してから `printf '%s' "$value" | vercel env add KEY production` の形で投入し直すこと。13 件すべてクリーンアップしてようやく、ビルドが安定しました。
git HEAD tree drift
もう 1 つ厄介だったのが、git のコミット履歴と実ファイルの乖離です。`git log lib/constants.ts` でコミットメッセージには載っているのに、`git ls-tree -r HEAD lib/` の結果に `lib/constants.ts` が出てこない、という現象が起きていました。
にもかかわらず本番サイトはちゃんと動いていました。理由は、Vercel のデプロイは git の HEAD ではなく作業ツリーから直接ファイルを送るため、git の tree が破損していてもデプロイには影響しないからです。
対策は、大きめの変更を一段落させたタイミングで `git ls-tree -r HEAD <ディレクトリ>` を実行し、必要なファイルが tree に含まれているか確認すること。drift していたら、明示的に `git add` してリンクし直すコミットを 1 本入れます。
クラウドホスティングと既存レンタルサーバー SMTP は、なぜ相性が悪いのか?
今回の経験で、構造的な学びとして残ったのは「クラウドホスティングと既存のレンタルサーバー SMTP は、原則として相性が悪い」ということです。
レンタルサーバーの SMTP は、そのサーバー内のアプリから使われることを前提に設計されています。一方、Vercel・Netlify・Cloudflare Pages・Firebase Hosting などのモダンホスティングは、世界中のクラウド IP で動的に動きます。スパム対策として送信元 IP ベースのブロックを行うレンタルサーバー側から見ると、両者は構造的に折り合いません。
この問題は Xserver に限った話ではなく、さくらインターネット、ロリポップ、お名前.com レンタルサーバー、ConoHa WING など、共用レンタルサーバー全般で起こり得ます。これらのホスティングを使ってきた個人事業主が「最近 Vercel に移したい」と思った時、必ず通る関門です。
Vercel / Netlify を使う個人事業主の SMTP 戦略
- 無料枠で十分な場合:Resend(月 3,000 通)または Brevo(旧 Sendinblue、月 9,000 通)
- 配送量が多い場合:SendGrid(月 100 通無料 / 有料は $19.95〜)
- AWS をすでに使っている場合:Amazon SES(月 62,000 通まで $0.10/1000 通)
- 日本語サポートを重視する場合:blastengine、ベアメールなど国内サービス
個人事業主の問い合わせフォーム用途であれば、Resend か Brevo の無料枠で長く運用できます。重要なのは「Vercel 上のコードから SMTP を直接打つ」ではなく、「メール配信に特化したサービスを 1 つ間に挟む」という発想の切り替えです。
まとめ:3 日間で得た教訓
再発防止になる学びは 3 つです。
- Vercel + 既存レンタルサーバー SMTP は IP フィルタで構造的に動かない。最初から Resend / SES / SendGrid を使う
- Resend を nodemailer から使う時は、SMTP_USER が固定文字列 "resend" であることに注意し、envelope.from には FROM_EMAIL(認証済みアドレス)を明示する
- 環境変数の改行汚染は echo ではなく printf で防ぐ。疑わしい時は vercel env pull して cat -A で末尾 $ を確認する
Vercel に移行してメール送信で詰まっているなら、迷わず Resend のドメイン認証から始めてください。3 日かかった遠回りを、1 時間に短縮できます。
OceansBase では、Vercel ホスティングや AI 活用での Web 制作、こうした落とし穴を回避する構成設計を専門としています。同じところで詰まっている方は、初回相談無料でご連絡ください。
関連記事
よくある質問
- Q. Vercel と Xserver の SMTP でメールが送れません。どうすればいいですか?
- A. Xserver の SMTP は AWS など外部クラウド IP からのリレーを 554 で一律拒否するため、Vercel から直接は送れません。Resend や SendGrid、Amazon SES などのメール配信サービスを 1 つ挟む構成に切り替えてください。コードはほぼそのまま使えます。
- Q. 個人事業主向けには Resend と SendGrid のどちらが向いていますか?
- A. 問い合わせフォーム程度の量なら Resend が向いています。月 3,000 通の無料枠と、ドメイン認証のシンプルさが魅力です。配送量が増えるなら SendGrid や Amazon SES、配信状況を細かく分析したいなら SendGrid を選ぶと無理がありません。
- Q. envelope.from とは何ですか?ヘッダの From とどう違うのですか?
- A. envelope.from は SMTP の MAIL FROM コマンドで指定する送信元アドレスで、バウンスの返送先になります。ヘッダの From は受信ボックスに表示される見た目の差出人です。Resend は envelope.from に認証済みドメインのアドレスを要求するため、ここが一致しないと拒否されます。
- Q. Resend は無料で使い続けられますか?
- A. 個人事業主の問い合わせフォーム用途であれば、Resend の無料枠(月 3,000 通 / 日 100 通)で長く使えます。月数十件程度の問い合わせ量なら有料化の必要はありません。配送量が増えてから Pro プランへ移行するで十分間に合います。
- Q. 独自ドメインから送るには何が必要ですか?
- A. Resend や SendGrid のダッシュボードで対象ドメインを追加し、指示される DNS レコード 3 種類(SPF / DKIM / バウンス受信用 MX)を DNS プロバイダーに登録します。浸透後にダッシュボードで Verify が通れば、noreply@ など好きなアドレスを差出人として使えるようになります。
関連記事
OceansBase
お気軽にご相談ください
OceansBase ではひとり事業者・フリーランス向けに Web 制作・AI 活用支援を行っています。
この記事のテーマで困っている場合はお問い合わせください。


