
本記事で紹介する「メールドメインのDNS存在チェック」は、Contact Form 7(CF7)の問い合わせフォームで、明らかなダミードメインを機械的に減らすための補助的な仕組みです(スパム対策の一部として使える場合があります)。
メールアドレスそのものの実在確認(いわゆる「メールアドレスの存在チェック」や個別のアカウント確認)を行うものではありません。また、DNS上でドメインが存在していても、受信側の設定や迷惑メール判定などにより、メールが届かない場合があります。
このため、本実装は「到達性の保証」ではなく、ノイズを減らすためのフィルターとして位置づけています(SMTP応答確認や実在アカウント確認は行いません)。
※ここで紹介するコードは学習・参考用のサンプルです。環境により動作が異なる場合があるため、本番導入前に必ずテスト環境で検証してください。
メールドメインのDNS存在チェック

また新しいコード書いたって?

メールの@以降をDNSで検証するだけの小物ツール。地味だけど効くやつ🐰💻

なるほど、ドメインだけ“存在するか”を先に見るんだね。
実装方針 取りこぼしを減らすDNSチェック設計
問い合わせフォームで、明らかなダミードメインを減らす補助策として、メールアドレスのドメインが“実在するか”をDNSで確認する方法があります。aaaa@aaaacccan.com のような明らかに存在しないドメインを早い段階で弾けるため、フォームの負荷や迷惑メールを減らせる場合があります。
-10.png)
ここでは Contact Form 7(CF7)に対して、次の流れで実装します。
という、現実運用で取りこぼしが少ない構成を紹介します。
なぜ「MXだけ」ではなく A/AAAA も見るの?
「メールの送受信」だけならMXが本筋…なんだけど、現場だと多くのドメインではMXを設定しますが、MXが未設定の場合に、環境や実装によって A/AAAA へフォールバックして扱われるケースもあるため、MXだけで即NGにすると取りこぼしが出る可能性があります。
そのため、MXだけで即NGにすると「実在するドメイン」を弾く可能性があるので、A/AAAAも併せて確認します。
そこで、
- MXがあればOK
- MXが無くても A/AAAA があれば「DNS上で応答があるドメイン」としてOK
にしておくと、日本企業の独自ドメインなども通しやすくなります。
※もちろん“メールが必ず届く保証”ではない。ここは後述
| 比較項目 | よくある実装 | 今回の実装例 |
| チェック対象 | MXレコードのみ MXのみ判定(※取りこぼしが出る可能性) | MX → A / AAAA 対応 (MXがなくても A/AAAAが存在すれば「DNS的に実在するドメイン」とみなして通す 安全設計) |
| 処理速度 | 毎回DNSに問い合わせる (フォーム送信ごとに遅延が出る場合がある。大量送信されるとDNS問い合わせが増え、サーバー負荷要因になり得る) | 結果をキャッシュ(Transient) (一度調べたドメインは一定時間WordPressに記憶させるため、2回目以降はDNS問い合わせを省けるため高速化しやすい) |
| 日本語ドメイン | エラーになる可能性大 ( 日本語.jp などが入力されると、DNS関数が正しく動作せず誤判定の原因になる) | Punycode変換対応 ( xn--... 形式に変換してからDNSチェックを行うため、多言語ドメインも正しく判定) |
| 汎用性 | フィールド名が固定 ( your-email 決め打ちなど、フォームの設定を変えると動かなくなる) | フィールド名を自動取得 (CF7のどのメールタグでも動作するように設計) |
どう実装する?
今回の構成は次の通りです。
1) バリデーションのタイミング
CF7のメール入力(email / email*)のバリデーションにフックして、送信前に判定します。
- 形式チェック(
xxx@yyy)はCF7本体に任せる - こちらは「ドメインの存在」だけに集中
2) ドメイン抽出
入力されたメールアドレスから @ 以降を取り出して小文字化します。
日本語ドメイン(IDN)があり得るので、環境が対応していれば ASCII(punycode) に変換してからDNSチェックします。
3) DNSチェック(MX→A/AAAA)
DNS関数で、
- MX がある → OK
- MX がない → A または AAAA がある → OK
- どれもない → NG
という判定。
4) DNS結果のキャッシュ
WordPressの Transient(set_transient / get_transient)で一定時間キャッシュします。
5) 失敗時のメッセージ
ここが大事で、「あなたのドメインは存在しません」みたいに断言しない。
現実には企業セキュリティやDNS応答の事情で、正しいドメインでも引っかかる可能性がゼロではないから。
おすすめの言い方はこれ:
実装サンプル ドメイン存在チェック部分
なぜMX以外も見るのか?
- MXが未設定でも、環境や実装によってはA/AAAAを参照してメール配送が行われる場合があります。そのため、MXのみで即NGとすると実在するドメインを弾いてしまう可能性があるため、本実装ではA/AAAAの存在も確認しています。
キャッシュの重要性
- DNS問い合わせ(
checkdnsrr)は、外部ネットワークとの通信が発生するため、PHPの処理の中では比較的「重い」処理です。 - 短時間に送信が集中すると、キャッシュがない場合はDNS問い合わせも増え、サーバー負荷要因になり得ます。また環境によっては、DNS側で制限がかかったり、失敗率が上がる可能性もあります。
- WordPressの Transient(set_transient / get_transient)を使い、同一ドメインへのDNS問い合わせを一定時間キャッシュします。
- (ログを併用する場合)ログにはメールアドレス等の個人情報が含まれ得るため、Webから直接参照できない場所に保存してください。
可能であればwebroot外へ。難しい場合はサーバ設定(.htaccess / Nginx など)で外部アクセスを拒否してください。
/**
* =========================================================
* Contact Form 7: DNS check for email domain (MX -> A/AAAA)
* - 目的:明らかなダミードメインを機械的に減らす(到達性保証ではない)
* - 形式チェック(xxx@yyy)はCF7本体に任せ、ここでは「ドメイン存在」だけを見る
* - 取りこぼしを減らすため MX が無くても A/AAAA があればOK扱い
* - DNS問い合わせは重いので transient でキャッシュ
* ※OKは長め / NGは短め(DNS一時障害での誤ブロック事故を減らす)
* =========================================================
*/
// 必須(email*)と任意(email)の両方のタグにフック
add_filter('wpcf7_validate_email', 'usaneko_cf7_validate_email_domain_dns', 10, 2);
add_filter('wpcf7_validate_email*', 'usaneko_cf7_validate_email_domain_dns', 10, 2);
/**
* CF7 バリデーション本体
*/
function usaneko_cf7_validate_email_domain_dns($result, $tag) {
// (任意)管理者のテスト送信は弾かない(※会員サイト等では運用に合わせて要調整)
if ( current_user_can('manage_options') ) return $result;
// タグ名取得(CF7のバージョン差:配列/オブジェクト両対応)
$name = '';
if ( is_object($tag) && isset($tag->name) ) {
$name = (string) $tag->name;
} elseif ( is_array($tag) && isset($tag['name']) ) {
$name = (string) $tag['name'];
}
if ( $name === '' ) return $result;
// POSTから取得(sanitize_emailで潰さず、rawからドメイン抽出する)
$email_raw = isset($_POST[$name]) ? trim((string) wp_unslash($_POST[$name])) : '';
if ( $email_raw === '' ) return $result;
// ドメイン抽出(IDN→punycode対応)
$domain = usaneko_cf7_extract_domain_from_email($email_raw);
if ( $domain === '' ) return $result;
// DNS存在チェック(MX → A/AAAA)
// ※ネットワークやDNSフィルタ環境により、正しいドメインでも確認できない場合があります
if ( ! usaneko_cf7_domain_dns_exists($domain) ) {
// 断定しない文言(「存在しません」ではなく「確認できませんでした」)
$result->invalidate(
$tag,
"メールアドレスのドメインを確認できませんでした。\n"
. "入力内容をご確認のうえ、別のアドレスでもお試しください。"
);
}
return $result;
}
/**
* 補助:ドメイン抽出 + IDN(punycode)変換
*/
function usaneko_cf7_extract_domain_from_email($email) {
$email = trim((string) $email);
// @ が無い場合は終了(形式チェックは基本CF7に任せる)
$pos = strrpos($email, '@');
if ( $pos === false ) return '';
$domain = strtolower(trim(substr($email, $pos + 1)));
if ( $domain === '' ) return '';
// 末尾ドット等のノイズ除去(例: example.com.)
$domain = rtrim($domain, '.');
// IDN(日本語ドメイン)→ Punycode
if ( function_exists('idn_to_ascii') ) {
$variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0;
$ascii = @idn_to_ascii($domain, 0, $variant);
if ( is_string($ascii) && $ascii !== '' ) {
$domain = $ascii;
}
}
return $domain;
}
/**
* 補助:DNSチェック(MX → A/AAAA)+ キャッシュ + ホワイトリスト
*/
function usaneko_cf7_domain_dns_exists($domain) {
$domain = strtolower(trim((string) $domain));
if ( $domain === '' ) return true;
// 例外的に通したいドメイン(必要なら追加)
$allow = array(
// 'example.local',
// 'intra.example',
);
if ( in_array($domain, $allow, true) ) return true;
// キャッシュキー生成(衝突防止)
$cache_key = 'usaneko_cf7_dns_' . md5($domain);
$cached = get_transient($cache_key);
if ( $cached !== false ) {
return ($cached === '1');
}
$ok = false;
// 1) checkdnsrr が使えるなら最優先(速い)
if ( function_exists('checkdnsrr') ) {
if ( @checkdnsrr($domain, 'MX') ) {
$ok = true;
} elseif ( @checkdnsrr($domain, 'A') || @checkdnsrr($domain, 'AAAA') ) {
$ok = true;
}
}
// 2) 保険:dns_get_record(環境差があるので警告抑制)
if ( ! $ok && function_exists('dns_get_record') ) {
$mx = @dns_get_record($domain, DNS_MX);
if ( ! empty($mx) ) {
$ok = true;
} else {
$a = @dns_get_record($domain, DNS_A);
if ( ! empty($a) ) {
$ok = true;
} else {
$aaaa = @dns_get_record($domain, DNS_AAAA);
if ( ! empty($aaaa) ) $ok = true;
}
}
}
// 3) それでも判断できない環境は「弾かない」保険(取りこぼし回避)
// ※厳しくしたいなら true → false に変更
if ( ! function_exists('checkdnsrr') && ! function_exists('dns_get_record') ) {
$ok = true;
}
// キャッシュ:
// - OK(存在する)は長め
// - NG(存在しない)は短め(DNS一時障害での誤ブロックを引きずらない)
$ttl = $ok ? 6 * 3600 : 10 * 60;
set_transient($cache_key, $ok ? '1' : '0', $ttl);
return $ok;
}
本チェックは“ドメインの存在確認”であり、メールが必ず届くことを保証するものではありません。
注意点
1) 届く保証はない
DNS上ドメインが存在しても、
- 迷惑メール判定(SPF/DKIM/DMARC)
- 受信側のフィルタ
- 相手のサーバ都合
で届かないことはあります。
2) ブロック文言は断定しない
正しいドメインでもDNS応答が不安定な環境はあります。
だから「存在しません」ではなく「確認できませんでした」が安全。
※企業ネットワークやDNSフィルタ環境では、正しいドメインでも確認できない場合があります。
3) キャッシュは必須(DNSは重い)
フォームアクセスが増えるとDNS問い合わせが積み上がります。
Transient等でキャッシュするのは実用上ほぼ必須。
4) 例外的に通したいドメインが出る可能性
企業内の特殊ドメインやテスト環境など、運用していると「通らないけど正しい」ケースがゼロではないです。
その時は、許可リスト(ホワイトリスト)を追加できる設計にしておくと保守が楽です。
5) ログを取る場合は個人情報に注意
メールアドレス等をログに残す場合は、マスク(例:us***@example.com のように一部のみ残す)や保存期間(例:30日でローテ)を決め、閲覧権限を限定してください。
この記事のまとめ
なお、本手法はスパムを“減らす”ための補助策であり、これ単体で完全に防げるものではありません。レート制限、ハニーポット、Turnstile/reCAPTCHA等と併用すると、全体のノイズ低減につながる場合があります。
WordPress+Cocoonを始めるなら「エックスサーバー」がおすすめ!
「これからWordPress+Cocoonでブログを作りたい!」という方には、
国内シェアNo.1の高速レンタルサーバー エックスサーバーがぴったりです。
- 🚀 WordPressクイックスタート対応(最短10分でブログ開設)
- 🪄 Cocoonテーマの自動インストールにも対応
- 🔒 無料独自SSL/高セキュリティ/自動バックアップ完備
- ☁️ 高速SSD・安定稼働率99.99%以上
- 💰️ スタンダード 月額料金 990円~ 693円~(キャンペーン中なら) 2025/11現在
「WordPress × Cocoon」を最もスムーズに始められる環境が整っています。
👉 今すぐチェック:レンタルサーバー エックスサーバー
最新のサービス価格は公式サイトでご確認ください。
免責事項
本記事のサンプルプログラムは動作・結果を保証するものではありません。 利用により発生したいかなるトラブル・損害についても、当方は責任を負いません。
