Mailgunは、パスワード再設定や注文確認のようなシステム発のメールをAPIで送るための配信サービスです。運営はスウェーデンのSinchで、HTTP APIとSMTPリレーの両方で送信でき、配信結果はWebhookで自社システムに返せます。この記事では、US・EUのエンドポイントの違い、SPF・DKIMの登録からcurlとNode.jsでの送信、HMAC-SHA256による署名検証と8時間の再送仕様、プランごとのログ保持日数までを公式ドキュメントにもとづいて整理します。SendGridやAmazon SESではなくMailgunを選ぶ条件も、送信量の試算とあわせて明示する構成です。
まとめ|Mailgunを選ぶ条件とAPI送信・Webhook受信で先に決める設計
Mailgunが向くのは、送信データをEU域内に閉じたい案件と、配信ログを自社DBに取り込む前提で組めるチームです。US・EUのリージョンはドメイン作成時に決まり、APIのベースURLもそれに従って変わります。後から移すには送信ドメインを作り直す必要があるため、最初の設計判断になります。
実装で先に決めるのは3点です。突合キー(注文番号やユーザーID)をv:変数で送信時に付けること、Webhookの署名をAPIキーとは別のWebhook Signing Keyで検証すること、成功時は204ではなく200を返すこと。BasicプランまではMailgun側のログ保持が1日しかないので、問い合わせ対応に使う履歴は自社側で持つ前提です。
AWS上で完結するシステムならAmazon SES、日本語の契約窓口が要るならSendGridの国内版を先に検討してください。
Mailgunの提供元と送信APIの構成、US・EUリージョンの違い
MailgunはマーケティングメールのUIよりも、開発者がAPIから呼ぶことを主眼に作られた製品です。画面でメルマガを組み立てる用途なら、同じグループのMailjetのほうが近い位置にあります。
Sinch傘下の開発者向けメールAPIとしての位置付けと提供機能の範囲
Mailgunは投資ファンドThoma Bravo傘下の米国企業でしたが、Sinchが2021年9月30日に発表した買収により、Mailjetとともに運営会社Pathwireごと約19億ドルでSinchの傘下に入りました。SMSや音声のAPIを持つ通信プラットフォームに、メールが加わった形です。
提供機能は、HTTP APIとSMTPによる送信、受信メールを解析して転送するRoutes、配信・開封・クリックなどのイベント通知、送信ログの検索に分かれます。メールの見た目を作るビジュアルエディタや購読者リストの管理は主役ではありません。業務システムから決まった文面を自動送信する用途に絞ると、機能の過不足が判断しやすくなります。
USとEUのAPIエンドポイントをドメイン作成時に決める理由と影響
Mailgunには米国とEUの2つのリージョンがあり、送信ドメインはどちらか一方に作成する仕組みです。公式のSend via HTTPは、EUリージョンのドメインではhttps://api.mailgun.netをhttps://api.eu.mailgun.netに置き換えるよう案内しています。SMTPのホスト名もsmtp.mailgun.orgとsmtp.eu.mailgun.orgに分かれます。
よくある事故は、EUで作ったドメインに対してUSのURLで送信し、ドメインが見つからないというエラーで止まるケースです。SDKの初期化にURLを渡し忘れても同じ症状になります。欧州の個人データを扱う案件でなければUSを選び、EUを選んだ場合は環境変数にベースURLを持たせて、コードに直書きしない構成にしておきます。
Mailgunでメールを送る手順|DNS認証からcurlとNode.jsでの送信まで
送信までの流れは、ドメインの追加、DNSレコードの登録と検証、APIキーの発行、送信リクエストの4段階です。DNSの反映待ちを除けば、作業は30分ほどで終わる分量です。
送信ドメインの追加とSPF・DKIMレコード登録で検証を通す手順
管理画面で送信ドメインを追加すると、DNSに登録すべきSPFとDKIMのTXTレコードが提示されます。ルートドメインではなくmg.example.co.jpのようなサブドメインを送信専用に切り出すのが一般的です。既存の社内メールとSPFレコードを共有せずに済み、配信の評価も分離できます。
SPFは1ドメインにつき1レコードという制約があるため、ルートドメインで送る場合は既存のレコードにincludeを追記する形になり、DNSの参照回数上限(10回)に触れやすくなります。SPFレコードの書き方と上限の考え方はSPFレコードの書き方と設定の判断基準で詳しく扱っています。検証が通るまでは送信できないので、DNSの管理者が別部署にいる場合は申請の段取りを先に組んでください。
curlでmessagesエンドポイントへPOSTする最小の送信リクエスト
送信APIは、ユーザー名にapi、パスワードにAPIキーを入れたBasic認証で、/v3/ドメイン名/messagesへフォーム形式でPOSTする作りです。差出人と宛先は環境変数で渡し、テスト中はo:testmodeを付けて実配信を止めます。
export MAILGUN_API_KEY="YOUR_API_KEY"
export MG_DOMAIN="mg.example.co.jp"
# MAIL_FROM と MAIL_TO も環境変数で設定しておく
curl -s --user "api:${MAILGUN_API_KEY}" \
"https://api.mailgun.net/v3/${MG_DOMAIN}/messages" \
-F from="${MAIL_FROM}" \
-F to="${MAIL_TO}" \
-F subject="パスワード再設定のご案内" \
-F text="30分以内に再設定を完了してください。" \
-F o:tag="password-reset" \
-F v:user_id="12345" \
-F o:testmode="yes"
成功すると受付IDを含むJSONが返りますが、これは受け付けただけで、届いたかどうかは後述のWebhookでしか分かりません。メッセージサイズの上限は25MBで、公式ドキュメントは圧縮前のボディに対して判定すると明記しています。添付ファイルを送る業務では、この上限を画面側の入力チェックにも反映させておきます。
mailgun.js 14.1系での送信とv:変数・o:tagで突合キーを持たせる実装
Node.jsでは公式SDKのmailgun.jsを使います。npmの最新は2026年11月時点で14.1系です。バージョン3.0以降は初期化時にFormDataを渡す仕様で、Node.js 18以降なら組み込みのものがそのまま使えます。
import Mailgun from "mailgun.js";
const mailgun = new Mailgun(FormData);
const mg = mailgun.client({
username: "api",
key: process.env.MAILGUN_API_KEY,
// EUリージョンのドメインなら次の行を有効にする
// url: "https://api.eu.mailgun.net",
});
const result = await mg.messages.create(process.env.MG_DOMAIN, {
from: process.env.MAIL_FROM,
to: [process.env.MAIL_TO],
subject: "ご注文を受け付けました",
text: "注文番号 A-1024 を受け付けました。",
"o:tag": ["order-confirm"],
"v:order_id": "A-1024",
});
console.log(result.id);
o:tagは管理画面での集計用、v:で始まる変数はWebhookのペイロードにuser-variablesとして戻ってくる値です。注文番号をここに入れておけば、bounceが届いたときにどの注文の通知が失敗したかを、メールアドレスの検索なしで特定できます。後から足せない値なので、送信処理を書く時点で決めておく設計事項です。
Compute Engineなどポート25が塞がれた環境でのSMTPリレー設定
既存のアプリがSMTP送信しかできない場合は、サーバーのPostfixからMailgunへリレーします。Google Cloudの公式チュートリアルは、Compute Engineではポート25が許可されないため2525番を使うよう案内しています。
# /etc/postfix/main.cf の末尾に追記
relayhost = [smtp.mailgun.org]:2525
smtp_tls_security_level = encrypt
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
認証情報はsasl_passwdに書いてpostmapでDB化し、平文のファイルは削除します。SMTP経由でもX-Mailgun-Variablesヘッダーを付ければ突合キーを渡せますが、改修できるアプリならHTTP APIへ寄せたほうがエラーの扱いが明快です。
Webhookで配信結果を受け取る実装|HMAC署名検証と8時間の再送
送信APIの応答は「受け付けた」までしか表しません。届いたか、恒久的に弾かれたかを業務データに反映するには、Webhookの受信口を用意します。公式のWebhooksの解説では、設定単位はアカウント全体とドメイン単位の2種類です。
delivered・failedなどイベント種別とseverityで分かれる失敗の扱い
Webhookを登録できるイベントは、accepted・delivered・temporary_fail・permanent_fail・opened・clicked・unsubscribed・complainedの8種類です。業務システムで最初に受けるべきは、deliveredとpermanent_failとcomplainedの3つ。開封とクリックはマーケティング指標なので、トランザクション用途では外して構いません。
注意したいのはペイロード上の名前です。Webhook Payloadsのサンプルでは、恒久的な失敗はeventがfailed、severityがpermanentという組み合わせで届きます。一時的な失敗もeventは同じfailedなので、severityを見ずに配信停止フラグを立てると、受信側サーバーの一時的な混雑だけで顧客への通知を止める事態になりかねません。公式はフィールドが今後増える可能性にも触れているので、生のJSONも丸ごと保存しておきます。
timestampとtokenのHMAC-SHA256で署名を検証するPythonコード
公式のSecuring Webhooksによると、署名はtimestampとtokenを区切りなしで連結した文字列のHMAC-SHA256です。鍵はAPIキーではなく、Settings内のAPI Securityで確認できるWebhook Signing Keyを使います。tokenは50文字のランダム文字列で、受信済みの値をキャッシュして2回目を処理しなければリプレイ攻撃を防げます。
import hashlib
import hmac
import os
from flask import Flask, abort, request
app = Flask(__name__)
SIGNING_KEY = os.environ["MAILGUN_WEBHOOK_SIGNING_KEY"].encode()
seen_tokens = set() # 本番はRedisなどにTTL付きで保存する
@app.post("/mailgun/events")
def mailgun_events():
payload = request.get_json(force=True)
sig = payload["signature"]
msg = (sig["timestamp"] + sig["token"]).encode()
expected = hmac.new(SIGNING_KEY, msg, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig["signature"]):
abort(403) # 鍵の設定ミスでも再送で取り戻せるよう406にはしない
if sig["token"] in seen_tokens:
return "", 200
seen_tokens.add(sig["token"])
enqueue(payload["event-data"]) # 保存はキューに逃がして即応答する
return "", 200
比較にはhmac.compare_digestを使い、文字列の==は避けます。タイムスタンプの鮮度チェックも可能ですが、公式はWebhookの処理遅延があり得ると注意しているので、数分単位の厳しい閾値は置きません。SendGridのEvent WebhookはECDSAの公開鍵で検証する方式で、SendGrid Event Webhookの署名検証と再送設計と比べると、Mailgunは共有鍵のHMACなので実装は短く済む反面、鍵の漏えい時に送信側へなりすませる点が異なります。
200と406だけが再送を止める仕様に合わせた応答コードの使い分け
公式のWebhook Retriesは、200を受け取れば成功、406を受け取れば拒否とみなして再送しないと定めています。それ以外の応答には、5分・10分・15分・1時間・2時間・4時間の間隔で、8時間にわたり再送が続きます。
この仕様から、応答コードの方針は次のように決まります。成功は204ではなく、公式が明記している200で返す。署名不一致は403にして、鍵のローテーション漏れのような自社側のミスなら8時間以内に直せば取り戻せる状態にする。406は、廃止したドメイン宛てのイベントのように二度と受け取る必要がないものだけに使う。SendGridの再送期間は最大24時間なので、両方を併用するシステムでは、障害の復旧目標をMailgun側の8時間に合わせておく必要があります。
MailgunとSendGrid・Amazon SESの選び分けと料金プランの読み方
3製品とも送信APIとイベント通知の基本機能は揃っています。差が出るのは、データの置き場所、契約と請求の形、ログを誰が持つかの3点です。
Free100通/日からScale月90ドルまで、プランごとのログ保持日数の差
Mailgunの料金ページで2026年11月1日時点の表記を確認すると、主なプランは次の4つです。請求はUSD建てで、EUR・GBPの表示にも切り替えられます。
| プラン | 月額 | 含まれる通数 | 超過1,000通 | ログ保持 |
|---|---|---|---|---|
| Free | 0ドル | 1日100通 | なし | 1日 |
| Basic | 15ドル | 月1万通 | 1.80ドル〜 | 1日 |
| Foundation | 35ドル | 月5万通 | 1.30ドル〜 | 5日 |
| Scale | 90ドル | 月10万通 | 1.10ドル〜 | 30日 |
BasicとFoundationの境目は月2.1万通あたりです。Basicで月3万通を送ると、15ドルに超過2万通分の36ドルが乗って51ドルになり、Foundationの35ドルを上回ります。もう1つ見落としやすいのがログ保持で、FreeとBasicは1日しか残りません。「先週送ったメールが届いていない」という問い合わせに管理画面で答えられないため、Webhookで受けたイベントを自社DBに残す実装が前提になります。専用IPはScaleに1つ含まれ、追加は1つあたり月59ドルとされています。
SendGridとAmazon SESに対してMailgunを選ぶ場面と見送る場面
Mailgunを選ぶのは、EU域内でのデータ処理を条件に挙げる顧客がいる場合と、AWS以外のクラウドで動くシステムからAPIで送りたい場合です。WebhookがHMACの数行で検証でき、送信もフォーム形式のPOST1本で済むため、既存の業務システムに後から組み込む部品としては扱いやすい部類に入ります。
見送るのは2つの場面です。システムがAWS上で完結しているなら、IAMでの権限管理とCloudWatchへの統合が効くAmazon SESの送信の仕組みとサンドボックス解除が先に候補になります。日本円での請求書払いや日本語の問い合わせ窓口が社内稟議の条件になっている会社なら、国内代理店経由の契約があるSendGridの直販と国内版の違いを確認してください。Mailgunを選んで後から請求形態で揉めるのは、技術ではなく契約の問題です。
Mailgun導入で詰まりやすい設計ミスと送信基盤を外部に相談する分岐点
失敗パターンとして多いのは、送信処理だけを先に作り、Webhookの受信と保存を後回しにする進め方です。Basicプランのログ保持は1日なので、受信口ができるまでの期間の配信結果は失われます。bounceしたアドレスへの送信を続けると送信ドメインの評価が下がり、正常な宛先への到達率まで落ちていきます。
判断を言い切ります。月の送信が数千通で、失敗を担当者がメールで知れば足りるなら、Webhookは作らず管理画面のアラートで済ませてください。配信結果をユーザーマスタの配信可否や業務画面の状態に反映するなら、受信・署名検証・冪等な保存・再送対応まで一式で設計します。複数のSaaSや基幹システムとイベントをやり取りする構成になる場合は、API開発・システム連携のページに、外部サービス連携の設計から実装までの対応範囲をまとめています。
よくある質問
Mailgunの導入検討で質問が多い論点を5つ取り上げます。
Mailgunは無料でどこまで使えますか?
2026年11月時点の料金ページでは、Freeプランは1日100通まで、カスタム送信ドメインは1つ、ログ保持は1日です。開発環境の動作確認や、社内向けの通知程度なら収まります。本番の会員向け通知で1日100通を超える可能性があるなら、上限到達で送信が止まる前提の設計は避け、Basic以上で契約してください。料金体系は過去に名称と価格が改定されているため、契約前に公式ページで最新の表記を確かめる手順を入れておくと安全です。
MailgunとMailjetは何が違いますか?
どちらもSinchのグループ製品ですが、狙っている利用者が異なります。MailgunはAPIやSMTPでシステムから送る開発者向けで、配信ログやWebhookなど組み込み用の機能が中心です。Mailjetはメールの見た目を画面で組み立て、購読者リストに一斉配信するマーケティング担当者向けの機能が厚くなっています。業務システムからの自動送信ならMailgun、メルマガ運用ならMailjetという分け方で迷うことはほとんどありません。
日本の宛先への到達率はSendGridやSESと比べて差がありますか?
到達率はサービスの名前よりも、送信ドメインのSPF・DKIM・DMARCの設定、bounceと苦情の処理、送信量の増やし方で決まります。どのサービスでも、認証が揃っていない状態で大量に送れば迷惑メールに振り分けられます。サービス間の比較を数値で示す公式データは各社とも出していないので、乗り換えで改善を期待するより、先に認証設定とbounce処理を点検するほうが確実です。
APIキーとWebhook Signing Keyはどう使い分けますか?
APIキーは送信やログ取得など、Mailgunに対して操作するときの認証に使います。Webhook Signing Keyは、Mailgunから届いたWebhookが本物かを自社側で確かめるための鍵です。公式ドキュメントも、署名の計算にAPIキーやドメインの送信キーを使わないよう明記しています。ローテーションの周期も漏えい時の影響範囲も異なるので、環境変数を分けて保管し、片方の更新でもう片方が止まらない構成にします。
Webhookの受信サーバーが止まっていた間のイベントは取り戻せますか?
Mailgunは200か406以外の応答に対して、8時間にわたり再送を続けます。その時間内に受信口を復旧させれば、止まっていた間のイベントも後から届きます。8時間を超えた分は再送されないため、Logs APIや管理画面のログから取り直すことになりますが、FreeとBasicはログが1日しか残りません。復旧の目標時間を8時間より内側に置き、受信口の死活監視を入れておくのが現実的な備えです。
関連記事
- センドグリッド(SendGrid)とは:直販と国内版の違いと最初の1通を送る実装手順
- SendGrid料金の実額と落とし穴:Freeプラン終了後の円建てプランと代替サービスの比較
- Amazon SESとは:SMTPとAPIの送信方式、サンドボックス解除と料金の考え方
- メール配信システムの自作・構築とは:SaaSに頼らずSMTP基盤を持つ場合の設計と到達率の確保
- DMARCのquarantineとrejectの違い:送信ドメインのなりすまし対策と設定状況の調査結果