「送信したメールが迷惑メールに入ってしまう」「相手に届かない」——そんなときに頼りになるのが Authentication-Results ヘッダです。これは受信側のメールサーバーが付与する"判定ログ"であり、SPF/DKIM/DMARCのどこで失敗したかを最短で特定できます。
本記事では、Authentication-Resultsの読み方を「型」として整理し、結果から修正アクションへ落とし込むまでの流れをコマンドと判定表でまとめます。
ゴールと前提
ゴール
ヘッダ1枚から原因箇所を特定し、修正アクションに落とす
メール認証のトラブルシューティングでは、問題の切り分けに時間がかかりがちです。本記事の手順に沿えば、Authentication-Resultsを見た瞬間に「どこを直せばいいか」が分かるようになります。
送信者がAuthentication-Resultsを確認する意味
blastengineなどのメール配信サービスを利用している場合、気になるのは「自分が送ったメールが相手にどう判定されているか」です。Authentication-Resultsは受信側が付与するヘッダなので、自分宛てにテストメールを送信し、そのヘッダを確認することで、本番配信前に認証状態をチェックできます。
到達率が低い・迷惑メールに入るといった問題の多くは、SPF/DKIM/DMARCの設定不備が原因です。テストメールのAuthentication-Resultsを確認すれば、DNS設定やalignmentの問題を事前に発見できます。
前提
- Authentication-Resultsは受信側のメールサーバーが付与します
- 同じメールでも、Gmail・Microsoft 365・Yahoo!メールなど受信サービスによって書式が微妙に異なります
- 本記事ではRFC 8601に準拠した一般的な書式を基準に解説します
まずはヘッダを取得する
トラブルシューティングの第一歩は「生ヘッダ」の取得です。メール本文ではなく、ヘッダ全文が必要になります。
テストメールを送信する
送信側として認証状態を確認するには、まず自分宛てにテストメールを送ります。
- 本番と同じ送信経路でメールを送信(blastengineのAPIやSMTPリレー経由)
- 複数の受信サービスで確認するのがベスト(Gmail、Microsoft 365など)
- 受信したメールの生ヘッダを取得
受信サービスによって判定が異なる場合があるため、主要な宛先(顧客が多く使っているサービス)でテストしておくと安心です。
なぜ生ヘッダが必要か
通常のメールクライアントでは、From・To・Subjectなど一部のヘッダしか表示されません。しかし、認証結果を確認するには以下のヘッダが必要です。
-
Authentication-Results: 認証判定の結果 -
Received: メールの配送経路 -
DKIM-Signature: DKIM署名の詳細 -
Return-Path: エンベロープFrom
主要サービスでの取得方法
| サービス | 操作手順 |
|---|---|
| Gmail | メールを開く → 右上の三点メニュー → 「原文を表示」 |
| Outlook (Web版) | メールを開く → 三点メニュー → 「表示」→「メッセージの詳細を表示」 |
| Outlook (Mac版) | メールを右クリック → 「ソースの表示」 |
| Yahoo!メール | メールを開く → 上部メニュー→その他→「詳細ヘッダー」をクリック |
| Apple Mail | メールを選択 → 「表示」→「メッセージ」→「すべてのヘッダ」 |
取得したヘッダの中から Authentication-Results: で始まる行を探します。
Authentication-Resultsの読み方
Authentication-Resultsは情報量が多いですが、読む順番を固定すれば迷いません。以下の「型」で読みましょう。
読む順番(型)
Authentication-Results: mx.google.com; ← ① authserv-id
dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com; ← ② dmarc
dkim=pass header.i=@example.com header.s=selector1 header.b=xxxxx; ← ③ dkim
spf=pass (google.com: ... ) smtp.mailfrom=bounce@example.com ← ④ spf
| 順番 | フィールド | 確認ポイント |
|---|---|---|
| ① | authserv-id | どのサーバーが判定したか(mx.google.com など) |
| ② | dmarc= | 最終結論。pass/fail/none のいずれか |
| ③ | dkim= | 署名検証の結果。header.d(署名ドメイン)と header.s(セレクタ)も確認 |
| ④ | spf= | 送信元IPの正当性。smtp.mailfrom(エンベロープFrom)も確認 |
判定結果の種類
| 結果 | 意味 |
|---|---|
| pass | 認証成功 |
| fail | 認証失敗(明示的に拒否される可能性あり) |
| softfail | 認証失敗だが、受信側の判断に委ねる(SPFで多用) |
| neutral | 認証情報なし、または判定を明言しない |
| none | レコードが存在しない |
| temperror | 一時的なエラー(DNS応答タイムアウトなど) |
| permerror | 永続的なエラー(レコードの文法エラーなど) |
実例1: オールpass(正常系の基準)
まず正常なAuthentication-Resultsを確認し、以降の異常例と比較する基準にします。
Authentication-Results: mx.google.com;
dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=example.com;
dkim=pass header.i=@example.com header.s=selector1 header.b=aBcDe123;
spf=pass (google.com: domain of info@example.com designates 203.0.113.10 as permitted sender)
smtp.mailfrom=info@example.com
チェックポイント
| 項目 | 値 | 判断 |
|---|---|---|
| dmarc | pass | SPFまたはDKIMがpassし、かつalignment(ドメイン一致)もOK |
| dkim | pass | 署名が有効。header.d=example.com がFromドメインと一致 |
| spf | pass | 送信元IP 203.0.113.10 がSPFレコードで許可されている |
| header.from | example.com | メールのFromヘッダのドメイン |
| smtp.mailfrom | info@example.com | エンベロープFromのドメイン(example.com) |
すべてpassかつドメインが揃っている——これが正常な状態です。
実例2: DKIM failのときにやること
Authentication-Results: mx.google.com;
dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=example.com;
dkim=fail (signature verification failed) header.i=@example.com header.s=selector1;
spf=pass smtp.mailfrom=info@example.com
dkim=fail が発生しています。原因を特定していきましょう。
Step 1: DKIM-Signatureヘッダを確認
同じメールの DKIM-Signature ヘッダを探します。
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=selector1;
h=from:to:subject:date:message-id;
bh=xxxxxx; b=yyyyyy
確認すべき値は以下の通りです。
-
d=: 署名ドメイン(example.com) -
s=: セレクタ(selector1) -
h=: 署名対象ヘッダ
Step 2: DNSでDKIMレコードを確認
dig selector1._domainkey.example.com TXT +short
期待される出力は以下の通りです。
"v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
よくある問題
- レコードが存在しない → セレクタ名の設定ミス
-
p=が空 → 鍵が無効化されている - 公開鍵の値が送信サーバーの秘密鍵とペアになっていない → 鍵の再生成が必要
Step 3: 署名対象ヘッダの改変を疑う
DNSレコードが正しいのにfailする場合、メール中継時にヘッダが改変された可能性があります。
| 改変されやすい箇所 | 原因例 |
|---|---|
| Subject | メーリングリストの接頭辞付与 [ML]
|
| From | 転送時の書き換え |
| 本文(body hash) | フッタ・免責事項の自動追加 |
| Content-Type | エンコード変換 |
対策
- 中継サーバーでのヘッダ・本文改変を確認
- 改変が避けられない場合は、中継後に再署名する設定を検討
実例3: SPF failのときにやること
Authentication-Results: mx.google.com;
dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=QUARANTINE) header.from=example.com;
dkim=pass header.i=@example.com header.s=selector1;
spf=fail (google.com: domain of info@example.com does not designate 198.51.100.25 as permitted sender)
smtp.mailfrom=info@example.com
SPFがfailし、DMARCもfailしています(DKIMはpassしていますが、alignmentの問題がある可能性)。
Step 1: smtp.mailfromを確認
エンベロープFrom(Return-Path)は info@example.com です。SPFはこのドメインのレコードで判定されます。
Step 2: 送信元IPを特定
Authentication-Resultsの記述から 198.51.100.25 が送信元IPと分かります。Receivedヘッダでも確認できます。
Received: from mail.sender.example (unknown [198.51.100.25])
by mx.google.com with ESMTPS id ...
Step 3: SPFレコードを確認
dig example.com TXT +short | grep spf
出力例
"v=spf1 include:_spf.google.com include:sendgrid.net ~all"
問題の特定
198.51.100.25 がこのSPFレコードで許可されているか確認します。
# includeされているドメインも展開して確認
dig _spf.google.com TXT +short
dig sendgrid.net TXT +short
よくある原因
| 原因 | 対策 |
|---|---|
| 新しい送信サーバーのIPが未登録 | SPFレコードに ip4:198.51.100.25 を追加 |
| 外部メール配信サービスのinclude漏れ |
include:spf.vendor.com を追加 |
| 転送サーバー経由で元IPが変わった | SRS(Sender Rewriting Scheme)の導入を検討 |
| DNSルックアップ回数が10回超過 | SPFレコードの簡素化(後述のpermerror参照) |
実例4: DMARC failのときにやること(alignment不一致)
Authentication-Results: mx.google.com;
dmarc=fail (p=REJECT sp=REJECT dis=REJECT) header.from=example.com;
dkim=pass header.i=@mail.example.com header.s=selector1;
spf=pass smtp.mailfrom=bounce@mail.example.com
SPFもDKIMもpassしているのに、DMARCがfail——これは典型的なalignment(アライメント)不一致です。
alignmentとは
DMARCは「SPFまたはDKIMがpassかつドメインが一致」していることを要求します。
| 認証 | 対象ドメイン | Fromドメインとの比較 |
|---|---|---|
| SPF | smtp.mailfrom(エンベロープFrom) | 一致が必要 |
| DKIM | header.d(署名ドメイン) | 一致が必要 |
この例での問題
| 項目 | 値 | Fromドメインとの一致 |
|---|---|---|
| header.from | example.com | (基準) |
| smtp.mailfrom | bounce@mail.example.com | 不一致(サブドメイン) |
| dkim header.d | mail.example.com | 不一致(サブドメイン) |
DMARCポリシーの aspf と adkim がデフォルト(strict)の場合、サブドメインは一致とみなされません。
修正の選択肢
| 選択肢 | 内容 | メリット/デメリット |
|---|---|---|
| A. DMARCポリシーを relaxed に |
aspf=r; adkim=r を追加 |
簡単だがセキュリティは若干低下 |
| B. DKIMの署名ドメインをFromに合わせる |
d=example.com で署名 |
推奨。From詐称への耐性維持 |
| C. エンベロープFromをFromに合わせる | Return-Pathを @example.com に |
バウンス管理の設計変更が必要 |
| D. Fromヘッダを署名ドメインに合わせる | From: を @mail.example.com に |
ブランドイメージへの影響を検討 |
推奨 選択肢B(DKIMの署名ドメインをFromドメインに合わせる)が最もバランスが良いです。
結果→修正アクション対応表
判定結果から修正箇所を素早く特定するための対応表です。
DKIM関連
| 結果 | 原因 | 修正アクション |
|---|---|---|
| dkim=fail | 署名検証失敗 | DNSの公開鍵確認 / 中継時の改変確認 |
| dkim=permerror | DNS/セレクタ設定エラー |
s=セレクタのDNSレコード確認、文法チェック |
| dkim=temperror | DNS一時障害 | 時間をおいて再確認 / DNSサーバーの状態確認 |
| dkim=none | 署名なし | 送信サーバーでDKIM署名を有効化 |
| dkim=neutral | 署名はあるが判定保留 | 署名設定の見直し |
SPF関連
| 結果 | 原因 | 修正アクション |
|---|---|---|
| spf=fail | 送信元IPが未許可 | SPFレコードにIP/includeを追加 |
| spf=softfail | 未許可だが拒否はしない設定 |
~all を意図的に使っているか確認 |
| spf=permerror | SPF文法エラー / ルックアップ10回超過 | レコード文法確認 / include展開数の削減 |
| spf=temperror | DNS一時障害 | 時間をおいて再確認 |
| spf=none | SPFレコードなし | SPFレコードを作成 |
DMARC関連
| 結果 | 原因 | 修正アクション |
|---|---|---|
| dmarc=fail | SPF/DKIMともにfail、またはalignment不一致 | SPF/DKIMの個別結果を確認 → alignment調整 |
| dmarc=none | DMARCレコードなし | DMARCレコードを作成 |
| dmarc=permerror | DMARCレコード文法エラー |
_dmarc.example.com のTXTレコード確認 |
確認コマンド(dig/openssl)
トラブルシューティングで使用する最小限のコマンド集です。
DNS確認
# SPFレコード確認
dig example.com TXT +short | grep -i spf
# DKIMレコード確認(セレクタ名は実際の値に置換)
dig selector1._domainkey.example.com TXT +short
# DMARCレコード確認
dig _dmarc.example.com TXT +short
# MXレコード確認(参考)
dig example.com MX +short
SPFレコードの展開確認
# includeされているドメインを再帰的に確認
dig _spf.google.com TXT +short
dig spf.protection.outlook.com TXT +short
# IPアドレスの逆引き確認(送信元特定に有用)
dig -x 203.0.113.10 +short
DKIM公開鍵の詳細確認
# 公開鍵を整形して確認
dig selector1._domainkey.example.com TXT +short | tr -d '"' | fold -w 64
継続監視: DMARCレポートを集計する
テストメールでの確認は単発のチェックに有効ですが、本番配信の認証状況を継続的に把握するにはDMARCレポートを活用します。DMARCレポートは、受信側(Gmail、Microsoft 365など)が送信側に送ってくれる認証結果の集計データです。
DMARCレポートとは
DMARCレコードに rua= タグを設定すると、受信側から定期的に集計レポート(Aggregate Report) が送られてきます。
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com"
| タグ | 意味 |
|---|---|
rua= |
集計レポートの送信先(mailto: または https:) |
ruf= |
失敗時の詳細レポート送信先(対応していない受信者も多い) |
レポートの中身
レポートはXML形式(gzip圧縮)で届きます。主要な情報は以下の通りです。
<record>
<row>
<source_ip>203.0.113.10</source_ip>
<count>1500</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
<identifiers>
<header_from>example.com</header_from>
</identifiers>
<auth_results>
<dkim>
<domain>example.com</domain>
<result>pass</result>
<selector>selector1</selector>
</dkim>
<spf>
<domain>example.com</domain>
<result>pass</result>
</spf>
</auth_results>
</record>
| フィールド | 内容 |
|---|---|
| source_ip | 送信元IP |
| count | そのIPからの送信数 |
| dkim/spf | 認証結果(pass/fail) |
| disposition | 受信側が適用したポリシー(none/quarantine/reject) |
Pythonでのパース例
import gzip
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from pathlib import Path
@dataclass
class DMARCRecord:
source_ip: str
count: int
header_from: str
dkim_result: str
spf_result: str
disposition: str
def parse_dmarc_report(file_path: str) -> list[DMARCRecord]:
"""DMARCレポート(gzip圧縮XMLまたは生XML)をパースする"""
path = Path(file_path)
# gzip圧縮か生XMLかを判定
if path.suffix == '.gz':
with gzip.open(file_path, 'rt', encoding='utf-8') as f:
tree = ET.parse(f)
else:
tree = ET.parse(file_path)
records = []
for record in tree.findall('.//record'):
row = record.find('row')
policy = row.find('policy_evaluated')
identifiers = record.find('identifiers')
records.append(DMARCRecord(
source_ip=row.findtext('source_ip', ''),
count=int(row.findtext('count', '0')),
header_from=identifiers.findtext('header_from', ''),
dkim_result=policy.findtext('dkim', ''),
spf_result=policy.findtext('spf', ''),
disposition=policy.findtext('disposition', ''),
))
return records
# 使用例
if __name__ == "__main__":
records = parse_dmarc_report('mail.ru!example.com!1759449600!1759536000.xml')
for r in records:
print(f"{r.source_ip}: DKIM={r.dkim_result}, SPF={r.spf_result}, count={r.count}")
集計・可視化
パースしたデータは以下のように活用できます。
- 送信元IPごとの認証成功率: 意図しないIPからの送信(なりすまし)を検出
- fail率の推移: 設定変更後の影響を確認
- ダッシュボード化: Grafana等で可視化し、閾値超過時にアラート
# 簡易集計の例
from collections import defaultdict
def summarize_records(records: list[DMARCRecord]) -> dict:
stats = defaultdict(lambda: {'pass': 0, 'fail': 0})
for r in records:
stats['dkim'][r.dkim_result] += r.count
stats['spf'][r.spf_result] += r.count
return dict(stats)
まとめ
Authentication-Resultsの読み方を「型」として整理しました。
読む順番(型)
- authserv-id: 誰が判定したか
- dmarc=: 最終結論
-
dkim=: 署名検証 +
header.d/header.s -
spf=: 送信元IP検証 +
smtp.mailfrom
トラブルシューティングの流れ
- メールの生ヘッダを取得
- Authentication-Resultsを「型」に沿って読む
- fail/permerrorがあれば、対応表から修正箇所を特定
- dig等でDNSレコードを確認・修正
この手順を身につければ、メールの到達率低下や迷惑メール判定の調査時間を大幅に短縮できます。問題が発生したら、まずAuthentication-Resultsを確認する習慣をつけましょう。