博客Email & SMTP
Email & SMTP

Email Deliverability in 2026: SPF, DKIM, DMARC, BIMI, and the Gmail/Yahoo Sender Requirements

Gmail and Yahoo now mandate SPF, DKIM, and DMARC for all bulk senders. Microsoft is following. This definitive guide covers correct configuration, troubleshooting failed deliveries, implementing BIMI for brand recognition, and monitoring your sender reputation.

E

Emily Watson

Technical Writer and Developer Advocate who simplifies complex technology for everyday readers.

March 18, 2026
21 分钟阅读

The 2024-2026 Email Authentication Revolution

In February 2024, Google and Yahoo enforced their bulk sender requirements: SPF or DKIM alignment, DMARC policy, one-click unsubscribe, and a spam rate below 0.3%. By 2025, Microsoft 365 followed with similar requirements for high-volume senders. Organizations that failed to comply saw delivery rates plummet overnight — in some cases, 30-40% of business email began routing to spam.

This guide covers every layer of email authentication, from the basics of how SPF/DKIM/DMARC work together, to advanced configurations that achieve 99%+ inbox placement, to BIMI implementation that puts your logo in Gmail inboxes.

The Email Authentication Stack Explained

How Email Authentication Works (The Big Picture)

Sender                     Receiving Server (Gmail)
  │                              │
  ├─ SMTP Connection ──────────→ │
  │                              ├─ 1. Check SPF
  │  From: marketing@company.com │   "Is this IP allowed to send for company.com?"
  │  DKIM-Signature: v=1; a=...  ├─ 2. Check DKIM
  │                              │   "Is this signature valid? Was email tampered?"
  │                              ├─ 3. Check DMARC
  │                              │   "Do SPF/DKIM align with From: header?
  │                              │    What should I do if they don't?"
  │                              ├─ 4. Check BIMI (if DMARC=quarantine/reject)
  │                              │   "Is there a verified logo to display?"
  │                              └─ 5. Apply policy + inbox/spam/reject

SPF: Sender Policy Framework

SPF authorizes specific IP addresses and mail servers to send email on behalf of your domain. It's a DNS TXT record that receiving servers check when your email arrives.

Writing a Correct SPF Record

# Basic SPF record structure
v=spf1 [mechanisms] [modifier]

# Mechanisms (what is authorized):
# ip4:1.2.3.4       - Specific IPv4 address
# ip4:1.2.3.0/24    - IPv4 CIDR range
# ip6:2001:db8::/32 - IPv6 range
# a                 - Domain's A record IPs
# mx                - Domain's MX record IPs
# include:domain    - Include another domain's SPF
# redirect=domain   - Delegate entirely to another domain's SPF

# Qualifiers (what to do when matched):
# + (pass, default) - Authorize this source
# - (fail)          - Unauthorized, should reject
# ~ (softfail)      - Probably unauthorized, soft reject
# ? (neutral)       - No policy

# EXAMPLE: Company using multiple services
# AWS SES + Google Workspace + Mailchimp + Sendgrid
v=spf1
  ip4:54.240.0.0/18          # AWS SES us-east-1
  include:_spf.google.com    # Google Workspace
  include:servers.mcsv.net   # Mailchimp
  include:sendgrid.net       # SendGrid
  ~all                       # Softfail everything else

SPF Lookup Limit Problem

SPF has a 10 DNS lookup limit. Modern stacks easily exceed this with multiple ESPs (Email Service Providers). Exceeding it causes SPF permerror — effectively a failure.

# Check your SPF lookup count
dig +short TXT company.com | grep spf

# Use dmarcian's SPF survey tool
curl "https://dmarcian.com/spf-survey/?domain=company.com"

# Count lookups manually:
# include: = 1 lookup per include
# a: = 1 lookup
# mx: = 1 lookup + 1 per MX record
# exists: = 1 lookup

# FIX: SPF flattening (resolve includes to IPs at publish time)
# Tools: mxtoolbox.com/spf, dmarcian, PowerSPF, AutoSPF
# Result:
v=spf1
  ip4:54.240.0.0/18
  ip4:74.125.0.0/16
  ip4:198.2.128.0/18
  ip4:149.72.0.0/16
  ip4:167.89.0.0/17
  ~all
# (flattened from 4 includes to direct IP ranges, 1 lookup total)

DKIM: DomainKeys Identified Mail

DKIM adds a cryptographic signature to every outbound email. The receiving server verifies the signature using a public key in your DNS. If email is tampered in transit, the signature breaks.

Generating and Configuring DKIM Keys

# Generate 2048-bit RSA key pair (minimum; use 4096 for new setups)
openssl genrsa -out dkim-private.pem 2048
openssl rsa -in dkim-private.pem -pubout -out dkim-public.pem

# Extract public key for DNS (remove header/footer, strip newlines)
openssl rsa -in dkim-private.pem -pubout -outform DER | openssl base64 -A

# DNS TXT record format:
# Selector: mail (can be any string, use date-based for rotation: 20260301)
# Record name: mail._domainkey.company.com
# Record value:
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...

# Postfix DKIM setup with OpenDKIM
sudo apt-get install opendkim opendkim-tools

# /etc/opendkim.conf
AutoRestart             Yes
AutoRestartRate         10/1h
UMask                   002
Syslog                  yes
LogWhy                  Yes
Canonicalization        relaxed/simple
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
InternalHosts           refile:/etc/opendkim/TrustedHosts
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
Mode                    sv
PidFile                 /var/run/opendkim/opendkim.pid
SignatureAlgorithm      rsa-sha256
UserID                  opendkim:opendkim
Socket                  inet:8891@localhost

# /etc/opendkim/KeyTable
mail._domainkey.company.com company.com:mail:/etc/opendkim/keys/company.com/mail.private

# /etc/opendkim/SigningTable
*@company.com mail._domainkey.company.com

# Test DKIM signing
echo "Test email" | mail -s "DKIM Test" test@gmail.com
# Check headers for DKIM-Signature field

DKIM Key Rotation (Security Best Practice)

# Rotate DKIM keys every 6-12 months

# Step 1: Generate new key with new selector (date-based)
opendkim-genkey -b 2048 -d company.com -s 20260301

# Step 2: Publish NEW key in DNS (both old and new coexist)
# 20260301._domainkey.company.com → new key
# mail._domainkey.company.com → old key (still valid)

# Step 3: Wait 48 hours for DNS propagation

# Step 4: Switch signing to new selector
# Update /etc/opendkim/KeyTable to use 20260301 selector

# Step 5: After 1 week (old emails still being validated)
# Remove old DNS record
# mail._domainkey.company.com → DELETE

# Step 6: Verify with MXToolbox
curl "https://mxtoolbox.com/dkim.aspx?domain=company.com&selector=20260301"

DMARC: Domain-Based Message Authentication, Reporting, and Conformance

DMARC ties SPF and DKIM together and tells receiving servers what to do when authentication fails. Critically, it also provides reporting — you receive daily XML reports showing who is sending email with your domain.

DMARC Record Configuration

# DNS TXT record at _dmarc.company.com

# Start permissive (monitoring only)
v=DMARC1; p=none; rua=mailto:dmarc@company.com; ruf=mailto:dmarc-forensic@company.com; fo=1

# After reviewing reports and fixing issues: quarantine
v=DMARC1; p=quarantine; pct=10; rua=mailto:dmarc@company.com; fo=1

# Gradually increase pct to 100%
v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@company.com

# Full enforcement (recommended end state)
v=DMARC1; p=reject; rua=mailto:dmarc@company.com; ruf=mailto:dmarc-forensic@company.com; fo=1; adkim=s; aspf=s

# Tag reference:
# p=none|quarantine|reject  Policy for failing messages
# pct=100                   Percentage of messages to apply policy to
# rua=mailto:...            Aggregate report destination
# ruf=mailto:...            Forensic report destination
# fo=0|1|d|s                Failure reporting options
# adkim=r|s                 DKIM alignment (relaxed/strict)
# aspf=r|s                  SPF alignment (relaxed/strict)
# sp=none|quarantine|reject Subdomain policy

DMARC Alignment Explained

STRICT alignment (adkim=s, aspf=s):
  DKIM domain MUST exactly match From: header domain
  company.com ≠ mail.company.com (FAILS)

RELAXED alignment (adkim=r, aspf=r) [DEFAULT]:
  DKIM/SPF domain just needs to be same organizational domain
  mail.company.com aligns with company.com (PASSES)

IMPORTANT for transactional email services:
  If Mailchimp sends FROM noreply@company.com:
    SPF: passes for Mailchimp IPs (included in your SPF)
    SPF alignment: Return-Path is @mc.com (NOT company.com) → FAILS alignment
    DKIM: Mailchimp signs with their key for bounce.company.com
    DKIM alignment: relaxed → bounce.company.com aligns with company.com → PASSES
    DMARC: only needs ONE of SPF/DKIM to align → PASSES (via DKIM)

Always enable DKIM signing in your ESP to ensure at least DKIM alignment passes.

Parsing DMARC Aggregate Reports

import zipfile
import xml.etree.ElementTree as ET
from email import message_from_bytes
import imaplib

def parse_dmarc_report(xml_content: str) -> dict:
    """Parse DMARC aggregate XML report"""
    root = ET.fromstring(xml_content)
    
    report = {
        'org_name': root.findtext('./report_metadata/org_name'),
        'date_range': {
            'begin': root.findtext('./report_metadata/date_range/begin'),
            'end': root.findtext('./report_metadata/date_range/end'),
        },
        'domain': root.findtext('./policy_published/domain'),
        'policy': root.findtext('./policy_published/p'),
        'records': []
    }
    
    for record in root.findall('./record'):
        source_ip = record.findtext('./row/source_ip')
        count = int(record.findtext('./row/count', 0))
        
        dkim_result = record.findtext('./row/policy_evaluated/dkim')
        spf_result = record.findtext('./row/policy_evaluated/spf')
        
        # Check if this is your legitimate sending IP
        is_passing = dkim_result == 'pass' or spf_result == 'pass'
        
        report['records'].append({
            'source_ip': source_ip,
            'count': count,
            'dkim': dkim_result,
            'spf': spf_result,
            'passing': is_passing,
        })
    
    # Find failing sources (potential spoofing)
    failing = [r for r in report['records'] if not r['passing']]
    if failing:
        print(f"WARNING: {len(failing)} failing sources found!")
        for r in failing:
            print(f"  IP: {r['source_ip']}, Count: {r['count']}, DKIM: {r['dkim']}, SPF: {r['spf']}")
    
    return report

BIMI: Brand Indicators for Message Identification

BIMI displays your company logo in Gmail, Apple Mail, and Yahoo Mail inboxes next to your email — but only if you have DMARC at p=quarantine or p=reject. For Gmail, you also need a Verified Mark Certificate (VMC).

BIMI Requirements Checklist

  • DMARC policy: p=quarantine or p=reject (not p=none)
  • SVG logo: Tiny PS (SVG Basic 1.1 subset), square aspect ratio, solid background
  • Logo hosted at HTTPS URL (accessible publicly)
  • VMC (Verified Mark Certificate) from DigiCert or Entrust — required for Gmail (~$1,500/year)
  • Trademark registration for your logo (required for VMC)
# BIMI DNS record at default._bimi.company.com
v=BIMI1; l=https://cdn.company.com/bimi-logo.svg; a=https://cdn.company.com/company-vmc.pem

# Without VMC (Yahoo/Apple Mail, not Gmail):
v=BIMI1; l=https://cdn.company.com/bimi-logo.svg

# SVG requirements for BIMI
# Must be SVG Tiny PS profile:
# - viewBox attribute required
# - No JavaScript
# - No external resources
# - No animations
# - Dimensions: 1:1 aspect ratio recommended

# Convert regular SVG to BIMI-compliant
# Install: npm install -g svgo
svgo --config svgo.config.js company-logo.svg -o company-bimi.svg

# Validate BIMI SVG
curl -X POST https://bimigroup.org/bimi-svg-validator/   -F "svg=@company-bimi.svg"

Subdomain Strategy for Email

Never send bulk email from your root domain (company.com). Use subdomains to protect your main domain's reputation.

Domain strategy:
  company.com          → Corporate email (employees, no bulk)
  mail.company.com     → Transactional email (receipts, notifications)
  news.company.com     → Marketing/newsletters
  bounce.company.com   → Bounce handling (set as Return-Path)

DNS records per subdomain:
  mail.company.com:
    MX  → mail.company.com.
    TXT → v=spf1 include:amazonses.com ~all
    TXT → v=DMARC1; p=reject; rua=mailto:dmarc@company.com
    TXT (at sendgrid._domainkey.mail.company.com) → DKIM public key

  news.company.com:
    TXT → v=spf1 include:servers.mcsv.net ~all  (Mailchimp)
    TXT → v=DMARC1; p=quarantine; rua=mailto:dmarc@company.com

  If company.com has no bulk sending:
    _dmarc.company.com → v=DMARC1; p=reject; sp=reject
    # sp=reject: subdomains inherit reject policy

Troubleshooting Deliverability Issues

Step-by-Step Diagnosis

# 1. Check authentication headers on received email
# Send test email to: check-auth-XXXXXXXX@verifier.port25.com
# Receive full auth report back

# 2. Test SPF
dig +short TXT company.com | grep spf
nslookup -type=TXT company.com
# Validate at: mxtoolbox.com/spf

# 3. Test DKIM
dig +short TXT mail._domainkey.company.com
# Validate: mxtoolbox.com/dkim (enter domain + selector)

# 4. Test DMARC
dig +short TXT _dmarc.company.com
# Full report: mxtoolbox.com/dmarc

# 5. Check sending IP reputation
# Check IP at: mxtoolbox.com/blacklists
curl "https://www.mxtoolbox.com/api/v1/Lookup/blacklist/?argument=203.0.113.5"

# Check Google Postmaster Tools
# https://postmaster.google.com - see domain reputation, spam rate

# 6. Investigate specific Gmail rejection
# Look for SMTP error codes:
# 550-5.7.26 — DMARC failure
# 421-4.7.28 — Newly created IP, warm up needed
# 550-5.7.1 — IP in Spamhaus PBL/SBL/XBL

IP Warmup Schedule

New dedicated IP warmup (never sent before):
Day 1:    200 emails
Day 2:    400 emails
Day 3:    800 emails
Day 4:  1,600 emails
Day 5:  3,200 emails
Day 6:  6,500 emails
Day 7: 13,000 emails
Day 8: 26,000 emails
Week 3: 100,000 emails/day
Week 4: Full volume

Rules:
- Only send to engaged subscribers (opened in last 90 days)
- Monitor bounce rate daily (>5% soft bounce: pause, >2% hard bounce: stop)
- Monitor spam complaints (>0.3%: stop and diagnose)
- Spread sending throughout the day (not all at 9am)

Gmail and Yahoo Requirements (2024+ Enforcement)

For senders sending >5,000 emails/day to Gmail:

REQUIRED:
  ✓ Valid forward and reverse DNS for sending IPs
  ✓ Valid TLS for transmitting email
  ✓ SPF OR DKIM authentication (both recommended)
  ✓ DMARC policy at p=none minimum (p=reject recommended)
  ✓ From: header matches DMARC-aligned domain
  ✓ One-click unsubscribe (RFC 8058: List-Unsubscribe-Post header)
  ✓ Process unsubscribe requests within 2 days
  ✓ Spam rate below 0.10% (above 0.30% = delivery problems)

DMARC enforcement timeline:
  2024-02: p=none required
  2024-06: Began applying p=quarantine for non-compliant senders
  2025-01: p=reject enforcement for bulk senders

List-Unsubscribe header implementation:
  List-Unsubscribe: <mailto:unsub@company.com?subject=unsub>, <https://company.com/unsub?id=USER_ID>
  List-Unsubscribe-Post: List-Unsubscribe=One-Click

Monitoring Dashboard Setup

# Weekly deliverability health check script
import smtplib
import dns.resolver
import requests

def check_deliverability_health(domain: str) -> dict:
    issues = []
    
    # Check SPF
    try:
        spf = dns.resolver.resolve(domain, 'TXT')
        spf_found = any('v=spf1' in str(r) for r in spf)
        if not spf_found:
            issues.append(f"CRITICAL: No SPF record for {domain}")
    except Exception as e:
        issues.append(f"CRITICAL: SPF lookup failed: {e}")
    
    # Check DMARC
    try:
        dmarc = dns.resolver.resolve(f"_dmarc.{domain}", 'TXT')
        dmarc_str = str(list(dmarc)[0])
        if 'p=none' in dmarc_str:
            issues.append(f"WARNING: DMARC p=none (not enforcing)")
        elif 'p=reject' in dmarc_str:
            print(f"✓ DMARC: p=reject (excellent)")
    except Exception as e:
        issues.append(f"CRITICAL: No DMARC record: {e}")
    
    # Check Spamhaus (simplified)
    # In production, use DNSBL query
    print(f"Deliverability check for {domain}: {len(issues)} issues found")
    for issue in issues:
        print(f"  {issue}")
    
    return {"domain": domain, "issues": issues}

check_deliverability_health("company.com")

Conclusion

Email authentication is no longer optional — it's table stakes for inbox delivery. The good news: SPF, DKIM, and DMARC have been around since 2012, the tooling is mature, and a disciplined implementation takes a few hours, not weeks.

The path forward: implement SPF and DKIM immediately, deploy DMARC at p=none with reporting, spend 2-4 weeks reviewing your aggregate reports to discover all your sending sources, then incrementally ramp to p=quarantine and finally p=reject. Once you're at p=reject, consider BIMI for brand recognition in email clients. Your delivery rates, sender reputation, and protection against domain spoofing will all improve significantly.

E

Emily Watson

Technical Writer and Developer Advocate who simplifies complex technology for everyday readers.

准备好改变您的基础设施了吗?

让我们讨论如何帮助您取得类似的成果。