Cloudflare SMTP でメール送信:ダッシュボード設定から Nodemailer の本番運用まで
Cloudflare Email Service の SMTP を使うための実践ガイド。ドメイン、API Token、cURL、Nodemailer、Python、制限、応答コード、再試行、安全対策を解説します。

Cloudflare Email Service は 2026 年に認証付き SMTP を追加しました。すでに Nodemailer、Python の smtplib、PHPMailer、または SMTP 設定しか持たない製品を使っている場合、送信層全体を作り直さずに配信サービスを切り替えられます。
この記事は独立した SMTP チュートリアルです。Email Routing、受信処理、Email Workers の全体像を繰り返すのではなく、次の一つを完成させます。
smtp.mx.cloudflare.net:465に接続し、Cloudflare API Token で SMTPS 認証を行い、自分のドメインからトランザクションメールを送信する。
内容は 2026 年 7 月 31 日時点で確認できた Cloudflare Email Service Beta に基づきます。Beta の割り当てや画面は変わる可能性があるため、本番導入前に公式の送信ガイドとSMTP API リファレンスを確認してください。
1 分で分かる接続設定
| 項目 | 値 |
|---|---|
| SMTP ホスト | smtp.mx.cloudflare.net |
| ポート | 465 |
| 暗号化 | 暗黙的 TLS、つまり SMTPS |
| ユーザー名 | 固定文字列 api_token |
| パスワード | Email Sending: Edit 権限を持つ Cloudflare API Token |
| 認証方式 | AUTH PLAIN または AUTH LOGIN |
| From | Email Sending で有効化したドメインのアドレス |
混同しやすい点が三つあります。
- ユーザー名は Cloudflare のメールアドレスや Account ID ではなく、常に
api_tokenです。 - パスワードは Cloudflare へのログインパスワードではなく、専用 API Token です。
- 465 番ポートは接続開始時から TLS を使います。587 番ポートの STARTTLS 設定とは交換できません。
Cloudflare は現在、587 STARTTLS や 25 番ポートの送信リレーを提供していません。25 番ポートは受信用 Email Routing のためのものです。現在の境界は公式 SMTP リファレンスで確認できます。
事前準備
1. 送信ドメインを Cloudflare DNS に置く
Email Sending には Cloudflare DNS が必要です。ダッシュボードで次を開きます。
Cloudflare Dashboard
→ Compute
→ Email Service
→ Email Sending
ドメインを選択してオンボーディングを完了します。Cloudflare は送信者認証、DKIM、バウンス処理に必要な DNS レコードを設定します。ドメインが「設定済み」になってから SMTP を調査してください。ログインに成功しても、送信ドメインが許可されていなければ拒否されます。
2. 最小権限の API Token を作る
ドメインの「接続」ページで SMTP を選択します。ダッシュボードには Email Sending: Edit 権限を持つ API Token の作成ボタンがあります。

2026 年 7 月 31 日に取得した画面です。Cloudflare の公開設定値と環境変数のプレースホルダーのみが含まれ、実際の Token は作成、表示、掲載していません。
アカウント所有の Token を優先し、可能な限り範囲を絞ります。
- メールを送る Cloudflare アカウントだけを許可する。
Email Sending: Editだけを付与する。- 適切な有効期限を設定する。
- デプロイ先の送信元 IP が固定なら、クライアント IP 制限も追加する。
- アプリごとに Token を分け、個別に監査・失効できるようにする。
完全な Token は作成時に一度だけ表示されます。サーバー側の Secret または実行時環境変数へ保存してください。次の場所には置けません。
- Git、Markdown、ビルドログ
NUXT_PUBLIC_*- ブラウザー JavaScript
- エラー時に出力する SMTP 設定オブジェクト
- 複数の無関係なアプリで共有する長期 Token
まず cURL で経路を検証する
最小の cURL テストを先に行うと、Cloudflare 側の設定エラーとフレームワーク統合エラーを分離できます。
mail.txt を作成します。
From: Jackie Moon <noreply@sparkles-editor.com>
To: recipient@example.com
Subject: Cloudflare SMTP test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Hello,
This message was delivered through Cloudflare Email Service SMTP.
Token の値をシェル履歴に残さず、現在のプロセスへ読み込みます。
read -s CF_EMAIL_SMTP_TOKEN
export CF_EMAIL_SMTP_TOKEN
送信します。
curl --url "smtps://smtp.mx.cloudflare.net:465" \
--ssl-reqd \
--user "api_token:${CF_EMAIL_SMTP_TOKEN}" \
--mail-from "noreply@sparkles-editor.com" \
--mail-rcpt "recipient@example.com" \
--upload-file mail.txt
成功すると SMTP は 250 を返します。応答に含まれる Message-ID を保存すると、Email Service ダッシュボードの送信ログと関連付けられます。Cloudflare の公式 cURL 例とも比較してください。
ここで失敗した場合、まだ Nodemailer を変更しないでください。
535:Token が無効、期限切れ、またはEmail Sending: Editがない。550:送信アドレスまたはドメインが許可されていない。552:メッセージが大きすぎる。421/451:一時的な障害またはレート制限。時間を置いて再試行する。
Node.js から Nodemailer を使う
この構成は Node.js、Docker、VPS、一般的な Serverless Function、または Node 互換環境で動く Nitro サーバー向けです。
依存関係を追加します。
pnpm add nodemailer
pnpm add -D @types/nodemailer
サーバー側の環境変数を定義します。
CF_EMAIL_SMTP_TOKEN=replace-with-a-server-side-secret
SMTP_FROM="Jackie Moon <noreply@sparkles-editor.com>"
Transport を作成します。
import nodemailer from 'nodemailer'
const transporter = nodemailer.createTransport({
host: 'smtp.mx.cloudflare.net',
port: 465,
secure: true,
auth: {
user: 'api_token',
pass: process.env.CF_EMAIL_SMTP_TOKEN,
},
connectionTimeout: 10_000,
greetingTimeout: 10_000,
socketTimeout: 30_000,
})
const info = await transporter.sendMail({
from: process.env.SMTP_FROM,
to: 'recipient@example.com',
subject: 'Welcome to Signal Log',
text: 'Your account is ready.',
html: '<p>Your account is ready.</p>',
})
console.info('Email accepted', {
messageId: info.messageId,
accepted: info.accepted.length,
rejected: info.rejected.length,
})

Cloudflare の画面例は process.env.CLOUDFLARE_API_TOKEN を使っています。プロジェクトではより具体的な変数名にできますが、値は必ずサーバー側に置きます。
本番コードには envelope の境界も必要です。
const allowedFrom = new Set([
'noreply@sparkles-editor.com',
'support@sparkles-editor.com',
])
function assertEmailEnvelope(from: string, to: string) {
if (!allowedFrom.has(from)) {
throw new Error('Sender is not allowed')
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(to)) {
throw new Error('Recipient is invalid')
}
}
任意の from、to、subject、HTML を受け取る公開 API は作らないでください。オープンリレーやフィッシング送信に悪用されます。安全な API は userId や templateId のような業務パラメータだけを受け取り、サーバーが検証済みの宛先を取得し、許可済みテンプレートから内容を生成します。
Nuxt が Cloudflare Workers 上で動く場合
実行環境を区別する必要があります。
Nodemailer は通常の Node SMTP Socket を対象にしたライブラリです。すでに Cloudflare Workers 上で動く Nuxt アプリは、通常 send_email binding を使う方が自然です。
interface Env {
EMAIL: SendEmail
}
await env.EMAIL.send({
from: 'noreply@sparkles-editor.com',
to: 'recipient@example.com',
subject: 'Verification code',
text: 'Your code is 123456',
})
Worker に SMTP API Token を保存せずに済み、binding 自体が権限境界になります。
選択基準は次のとおりです。
| 状況 | 推奨する入口 |
|---|---|
| Nuxt / API が Cloudflare Workers 上にある | Workers の send_email binding |
| 外部サービスが HTTPS と Token 管理に適している | REST API |
| 既存の Node、Python、PHP アプリが SMTP に対応済み | 認証付き SMTP |
| プリンター、旧 CMS、外部製品が SMTP 設定しか持たない | 465 SMTPS 対応を確認して SMTP |
| ブラウザーから直接送る | 禁止。管理されたサーバー API を使う |
三つのサーバー側入口は Cloudflare の配信、ログ、ドメイン認証へ合流します。SMTP の価値は互換性であり、すべての新規サービスを Socket に戻すことではありません。
Python の最小例
Python の標準ライブラリだけで送信できます。
import os
import smtplib
from email.message import EmailMessage
token = os.environ["CF_EMAIL_SMTP_TOKEN"]
message = EmailMessage()
message["From"] = "Jackie Moon <noreply@sparkles-editor.com>"
message["To"] = "recipient@example.com"
message["Subject"] = "Cloudflare SMTP test"
message.set_content("This email was sent over implicit TLS.")
with smtplib.SMTP_SSL("smtp.mx.cloudflare.net", 465, timeout=30) as smtp:
smtp.login("api_token", token)
smtp.send_message(message)
重要なのは SMTP_SSL であり、平文接続の後に starttls() を呼ぶ構成ではありません。
制限と添付ファイル
現在のEmail Service プラットフォーム制限は次のとおりです。
- 1 通あたりの宛先合計は最大 50。
- SMTP セッションで受け付ける
RCPT TOは最大 50 回。 - 通常のメッセージ総容量は最大 5 MiB。
- 検証済み宛先へのメッセージは最大 25 MiB。
- SMTP
AUTHのタイムアウトは 30 秒。 - SMTP
DATAのタイムアウトは 300 秒。 - カスタムヘッダー合計は 16 KiB。
- Subject は 998 文字まで。
総容量には MIME エンコード後の本文と添付ファイルが含まれます。Base64 ではバイナリが約 3 分の 1 増えることが多いため、4.9 MiB のファイルが 5 MiB のメールに収まるとは限りません。
ダッシュボードの日次割り当ては、アカウント、Beta 段階、利用状況によって変わる可能性があります。一度見た値をハードコードせず、現在の画面を確認して送信キューに余裕を残します。
エラー処理と再試行
SMTP 応答は再試行すべきかを表しています。
| 応答 | 意味 | 対応 |
|---|---|---|
250 | Cloudflare が受理した | Message-ID を保存し、重複送信しない |
421 / 451 | 一時障害または制限 | ジッター付き指数バックオフ |
535 | 認証失敗 | 自動再試行を止め、Token を確認 |
550 | 送信者、ドメイン、宛先ポリシーによる拒否 | 設定またはデータを直してから送る |
552 | メッセージが大きすぎる | 本文または添付を減らす |
再試行は 30 秒、2 分、10 分、30 分のようにし、最大回数を固定します。業務上の送信には冪等キーが必要です。
password-reset:{userId}:{resetRequestId}
invoice:{invoiceId}:issued
一時エラーはキューへ、恒久エラーは失敗または確認状態へ送ります。すべての非 250 を即時再試行したり、ネットワークの揺れで同じ認証コードを十回送ったりしてはいけません。
250 は最終到達ではない
250 は Cloudflare が受理したという意味で、最終受信箱への到着を保証しません。本番環境では次を監視します。
- Delivery、Bounce、Deferred、Suppression
- Gmail、Outlook、QQ メールなど主要宛先の結果
- From、Return-Path、DKIM、DMARC の整合
- ハードバウンスした宛先の即時停止
- 苦情率と無効アドレスの増加
- 認証コードとパスワード再設定メールの明確な有効期限
トランザクションメールには安定して識別できる送信者を使い、テキスト版と HTML 版を用意します。Reply-To を偽装せず、認証用経路で広告を大量送信せず、ユーザー入力をメールヘッダーへ直接入れないでください。
本番チェックリスト
- 送信ドメインが Email Sending で設定済み
- 465 番ポートと暗黙的 TLS を使用
- ユーザー名が正確に
api_token - Token の権限が
Email Sending: Editのみ - Token がサーバー Secret にあり、Git、ログ、フロントにない
- From はサーバー側 allowlist から選ぶ
- 宛先を検証、重複排除、長さ制限している
- HTML は固定テンプレートで、テキスト版もある
-
421/451はジッター付き指数バックオフ -
535/550/552を無条件再試行しない - Message-ID、テンプレート名、冪等キーを記録し、Token は記録しない
- バウンス、抑制、苦情、日次割り当てを監視
最後の選択
安定稼働中の Node、Python、PHP アプリでは、Cloudflare SMTP の利点は明快です。ホスト、ポート、認証情報を変更するだけで Cloudflare の配信とログへ接続できます。
Workers ネイティブのアプリなら、従来サーバーを再現する必要はありません。Email binding を優先してください。SMTP は既存エコシステムへの橋であり、唯一の入口ではありません。
コメント
ログインして議論に参加
メールアドレスは公開されず、表示名だけが表示されます。