Skip to main content
โšก Calmops

SPF DKIM DMARC: Complete Email Authentication Guide for 2026

Introduction

Email remains one of the most critical communication channels for businesses, yet it remains vulnerable to spoofing, phishing, and impersonation attacks. Email authentication protocols - SPF, DKIM, and DMARC - provide the foundation for email security by verifying that emails actually come from the claimed sender.

This comprehensive guide covers everything you need to know about implementing and maintaining email authentication. You’ll learn how each protocol works, how they complement each other, and practical steps for deployment in production environments.

With email-based attacks on the rise - phishing attempts increased by 48% in 2025 - implementing proper email authentication is no longer optional. Major email providers like Google and Yahoo now require authentication for bulk senders, making these protocols essential for anyone sending email.

Understanding Email Authentication

Why Email Authentication Matters

Email spoofing allows attackers to forge the sender address, making emails appear to come from trusted sources. This enables:

  • Phishing attacks: Credible-looking emails stealing credentials
  • Business email compromise (BEC): Fraudulent requests for money or data
  • Brand impersonation: Fake emails damaging brand reputation
  • Malware distribution: Malicious attachments appearing from trusted senders

The Three Pillars of Email Authentication

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Email Authentication Stack                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                       โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚   โ”‚                    DMARC (Policy Layer)                      โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Defines policy for handling auth failures                โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Provides reporting and alignment                         โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Builds on SPF and DKIM results                          โ”‚   โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                              โ”‚                                       โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚   โ”‚                   DKIM (Signature Layer)                     โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Cryptographic signing of email content                   โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Verifies email wasn't modified in transit                โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Persistent across forwarding                              โ”‚   โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                              โ”‚                                       โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚   โ”‚                    SPF (Authorization)                       โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Authorizes specific servers to send email                โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Simple IP-based verification                              โ”‚   โ”‚
โ”‚   โ”‚  โ€ข Fast and lightweight                                      โ”‚   โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ”‚                                                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

SPF (Sender Policy Framework)

How SPF Works

SPF allows domain owners to specify which mail servers are authorized to send email on behalf of their domain. When receiving mail, the receiving server checks if the sending server’s IP is listed in the domain’s SPF record.

SPF Workflow:

  1. Domain owner publishes SPF record in DNS
  2. Sending server sends email, declaring its domain in the “Mail From” header
  3. Receiving server looks up SPF record for sending domain
  4. Server verifies sending IP is authorized
  5. Email is accepted, rejected, or marked based on policy

SPF Record Syntax

v=spf1 ip4:192.0.2.0/24 include:_spf.google.com ~all

Record Components:

Component Description
v=spf1 SPF version (always spf1)
ip4:x.x.x.x Specific IPv4 address
ip4:x.x.x.x/n CIDR block
include:domain Include another domain’s SPF
a A record matches sending server
mx MX server matches
all End of rules (fail, softfail, pass)
~all Softfail (mark as spam)
-all Hardfail (reject)
?all Neutral (no policy)

Common SPF Configurations

Simple Single Server:

v=spf1 ip4:203.0.113.10 -all

Multiple Sending Services:

v=spf1 ip4:203.0.113.10 include:_spf.google.com include:_spf.mailchimp.com -all

Cloud Email (Google, Microsoft, etc.):

v=spf1 include:_spf.google.com include:_dmarc.microsoft.com ~all

Complex Domain with Multiple Sources:

v=spf1 
    ip4:203.0.113.0/24 
    ip4:198.51.100.0/24 
    include:_spf.google.com 
    include:spf.protection.outlook.com 
    include:servers.mcsv.net 
    -all

SPF Best Practices

  1. Keep it simple: Too many includes can cause issues
  2. Use -all (hardfail): More secure than softfail
  3. Limit lookups: SPF has a 10 DNS lookup limit
  4. Monitor before enforcing: Start with ~all, move to -all
  5. Include all senders: Check marketing tools, CRM, etc.

SPF Verification

import dns.resolver
import ipaddress


def verify_spf(domain: str, sending_ip: str) -> dict:
    """Verify if IP is authorized by SPF."""
    
    try:
        # Get SPF record
        answers = dns.resolver.resolve(domain, 'TXT')
        spf_record = None
        
        for rdata in answers:
            if 'v=spf1' in str(rdata):
                spf_record = str(rdata).strip('"')
                break
        
        if not spf_record:
            return {'status': 'none', 'message': 'No SPF record'}
        
        # Check if IP is authorized
        sending = ipaddress.ip_address(sending_ip)
        
        # Parse SPF and check IP
        qualifiers = {'+': 'pass', '-': 'fail', '~': 'softfail', '?': 'neutral'}
        
        # Simplified parsing
        parts = spf_record.split()
        for part in parts[1:]:  # Skip v=spf1
            if part.startswith('ip4:'):
                cidr = part.replace('ip4:', '')
                network = ipaddress.ip_network(cidr, strict=False)
                if sending in network:
                    return {'status': qualifiers.get('+', 'pass'), 'record': spf_record}
        
        return {'status': 'fail', 'record': spf_record}
        
    except Exception as e:
        return {'status': 'error', 'message': str(e)}


# Usage
result = verify_spf('example.com', '203.0.113.10')
print(result)

DKIM (DomainKeys Identified Mail)

How DKIM Works

DKIM uses public-key cryptography to sign emails. The sending server signs the email headers using a private key, and receiving servers verify the signature using the public key published in DNS.

DKIM Workflow:

  1. Domain owner generates public/private key pair
  2. Public key is published in DNS as TXT record
  3. Sending server signs email headers with private key
  4. Signature is added to email headers
  5. Receiving server fetches public key from DNS
  6. Signature is verified, confirming email wasn’t tampered with

DKIM Record Syntax

default._domainkey.example.com IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEF"

Record Components:

Component Description
v=DKIM1 DKIM version
k=rsa Key type (rsa, ed25519)
p= Public key (base64 encoded)
h= Hash algorithms (sha256, sha1)
s= Service type (*)
t= Flags (y = testing)

Generating DKIM Keys

# Generate 2048-bit RSA DKIM key
openssl genrsa -out dkim_private.pem 2048

# Extract public key
openssl rsa -in dkim_private.pem -pubout -out dkim_public.pem

# Format for DNS (one line)
openssl rsa -in dkim_private.pem -pubout -outform DER 2>/dev/null | \
    openssl base64 -A | fold -w 64

DKIM Implementation

Postfix Configuration:

# /etc/postfix/main.cf

# Enable DKIM signing
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

# Or for OpenDKIM
# /etc/opendkim.conf

Domain                  example.com
KeyFile                 /etc/opendkim/keys/example.com/private
Selector                default
Mode                    sv
Canonicalization        relaxed/simple

OpenDKIM Configuration:

# /etc/opendkim.conf
Syslog                  yes
UMask                   007
KeyTable                refile:/etc/opendkim/key.table
SigningTable            refile:/etc/opendkim/signing.table
ExternalIgnoreList      refile:/etc/opendkim/trusted.hosts
InternalHosts           refile:/etc/opendkim/trusted.hosts

# /etc/opendkim/key.table
default._domainkey.example.com   example.com:default:/etc/opendkim/keys/example.com/default.private

# /etc/opendkim/signing.table
*@example.com    default._domainkey.example.com

Python DKIM Library:

import dkim


def sign_email(message: bytes, selector: str, domain: str, privkey: str) -> str:
    """Sign email with DKIM."""
    
    sig = dkim.sign(
        message,
        selector.encode(),
        domain.encode(),
        privkey.encode()
    )
    
    return sig.decode()


def verify_email(message: bytes) -> dict:
    """Verify DKIM signature."""
    
    try:
        dkim.verify(message, None)
        return {'status': 'valid'}
    except dkim.DKIMVerifyError as e:
        return {'status': 'invalid', 'error': str(e)}

DKIM Best Practices

  1. Use 2048-bit keys: More secure than 1024-bit
  2. Consider ED25519: Modern algorithm, smaller keys
  3. Rotate keys regularly: Annual rotation recommended
  4. Use sha256: More secure than sha1
  5. Sign essential headers: From, To, Subject, Date

DMARC (Domain-based Message Authentication)

How DMARC Works

DMARC builds on SPF and DKIM by providing:

  1. Policy: What to do with failing emails
  2. Alignment: Ensures SPF/DKIM domain matches “From” address
  3. Reporting: Visibility into email authentication results

DMARC Workflow:

  1. Domain owner publishes DMARC policy in DNS
  2. Receiving server checks SPF and DKIM
  3. Server checks if either aligns with “From” header
  4. Policy is applied based on results
  5. Aggregate reports sent to domain owner

DMARC Record Syntax

_dmarc.example.com IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]; ruf=mailto:[email protected]; pct=100"

Record Components:

Component Description
v=DMARC1 DMARC version
p= Policy (none, quarantine, reject)
pct= Percentage of messages to apply policy
rua= Aggregate report destination
ruf= Forensic report destination
sp= Subdomain policy
adkim= DKIM alignment (r = relaxed, s = strict)
aspf= SPF alignment (r = relaxed, s = strict)

DMARC Policies

Monitoring Only (none):

v=DMARC1; p=none; rua=mailto:[email protected]

Quarantine (suspicious emails):

v=DMARC1; p=quarantine; rua=mailto:[email protected]; pct=100

Reject (best protection):

v=DMARC1; p=reject; rua=mailto:[email protected]; pct=100

DMARC Alignment

Alignment ensures the “From” header matches the authenticated domain:

Relaxed Alignment (recommended):

v=DMARC1; p=reject; adkim=r; aspf=r; -all

Strict Alignment:

v=DMARC1; p=reject; adkim=s; aspf=s; -all

DMARC Reports

Aggregate Reports (rua):

<?xml version="1.0" encoding="UTF-8"?>
<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>[email protected]</email>
    <date_range>
      <begin>1640995200</begin>
      <end>1641081600</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.com</domain>
    <adkim>r</adkim>
    <aspf>r</aspf>
    <p>reject</p>
    <pct>100</p>
  </policy_published>
  <record>
    <source_ip>203.0.113.10</source_ip>
    <count>5</count>
    <disposition>none</disposition>
    <dkim>pass</dkim>
    <spf>pass</spf>
  </record>
</feedback>

Processing DMARC Reports

import xml.etree.ElementTree as ET
from dataclasses import dataclass
from typing import List


@dataclass
class DMARCResult:
    """Parsed DMARC report result."""
    source_ip: str
    count: int
    disposition: str
    dkim_result: str
    spf_result: str


def parse_aggregate_report(xml_content: str) -> List[DMARCResult]:
    """Parse DMARC aggregate report."""
    
    results = []
    root = ET.fromstring(xml_content)
    
    for record in root.findall('.//record'):
        source_ip = record.find('source_ip').text
        count = int(record.find('count').text)
        disposition = record.find('disposition').text
        dkim = record.find('dkim').text
        spf = record.find('spf').text
        
        results.append(DMARCResult(
            source_ip=source_ip,
            count=count,
            disposition=disposition,
            dkim_result=dkim,
            spf_result=spf
        ))
    
    return results


def summarize_dmarc_report(report: List[DMARCResult]) -> dict:
    """Generate summary of DMARC report."""
    
    total = sum(r.count for r in report)
    passed = sum(r.count for r in report if r.dkim_result == 'pass' and r.spf_result == 'pass')
    failed = total - passed
    
    return {
        'total_emails': total,
        'passed': passed,
        'failed': failed,
        'pass_rate': f"{passed/total*100:.1f}%" if total > 0 else "0%",
        'unique_sources': len(set(r.source_ip for r in report))
    }

Complete Implementation Guide

Step 1: Audit Your Email Sending

Before implementing, understand your sending sources:

# Check all services sending email for your domain
sending_sources = {
    "google_workspace": {
        "include": "_spf.google.com",
        "description": "Gmail/G Suite"
    },
    "office_365": {
        "include": "spf.protection.outlook.com", 
        "description": "Microsoft 365"
    },
    "mailchimp": {
        "include": "servers.mcsv.net",
        "description": "Email marketing"
    },
    "sendgrid": {
        "include": "sendgrid.net",
        "description": "Transactional email"
    },
    "custom_server": {
        "ip": "203.0.113.10",
        "description": "On-premise mail server"
    }
}

Step 2: Publish SPF Record

v=spf1 \
    include:_spf.google.com \
    include:spf.protection.outlook.com \
    include:servers.mcsv.net \
    include:sendgrid.net \
    ip4:203.0.113.10 \
    -all

Step 3: Set Up DKIM

  1. Generate DKIM keys for each sending service
  2. Add DKIM records to your DNS
  3. Test with external tools
# Test DKIM with opendkim-testkey
opendkim-testkey -d example.com -s default -vvv

Step 4: Configure DMARC

Start with monitoring, then enforce:

Week 1-2: Monitor:

v=DMARC1; p=none; rua=mailto:[email protected]; pct=25

Week 3-4: Quarantine:

v=DMARC1; p=quarantine; rua=mailto:[email protected]; ruf=mailto:[email protected]; pct=50

Ongoing: Reject:

v=DMARC1; p=reject; rua=mailto:[email protected]; ruf=mailto:[email protected]; pct=100

Step 5: Monitor and Adjust

# Weekly monitoring checklist
monitoring_checklist = {
    "check_rua_reports": "Review DMARC aggregate reports",
    "verify_dkim_selectors": "Ensure all selectors are valid",
    "update_spf": "Add new sending sources",
    "check_failures": "Investigate authentication failures",
    "update_policy": "Increase enforcement as confidence grows"
}

Testing Email Authentication

Command Line Testing

# Check SPF record
dig TXT example.com +short

# Check DKIM record
dig TXT default._domainkey.example.com +short

# Check DMARC record  
dig TXT _dmarc.example.com +short

# Check email headers
nslookup -type=txt example.com

# Use mail-tester.com for complete analysis
curl mail-tester.com
# Send test email to address provided

Online Testing Tools

Tool URL Features
mail-tester.com mail-tester.com Complete analysis
DKIM Validator dkimvalidator.com DKIM checks
DMARC Analyzer dmarc-analyzer.ninja DMARC reports
MXToolbox mxtoolbox.com All checks

Automated Monitoring

import schedule
import time


def monitor_email_auth():
    """Daily email authentication monitoring."""
    
    domains = ['example.com', 'sub.example.com']
    
    for domain in domains:
        # Check SPF
        spf_status = check_spf(domain)
        
        # Check DKIM
        dkim_status = check_dkim(domain)
        
        # Check DMARC
        dmarc_status = check_dmarc(domain)
        
        # Alert on issues
        if spf_status != 'pass' or dkim_status != 'pass':
            send_alert(f"Email auth issue for {domain}")
        
        # Log status
        log_status(domain, spf_status, dkim_status, dmarc_status)


schedule.every().day.at("09:00").do(monitor_email_auth)

while True:
    schedule.run_pending()
    time.sleep(60)

Troubleshooting Common Issues

SPF Issues

Issue Solution
Too many DNS lookups Reduce includes, use IP ranges
Softfail instead of hardfail Change ~all to -all
Missing senders Audit all email sources
Forwarding breaks Use ARC headers

DKIM Issues

Issue Solution
Invalid signature Check key format, verify selector
Key too short Use 2048-bit minimum
Headers not signed Configure to sign From header
Rotation broke signing Update DNS before retiring old key

DMARC Issues

Issue Solution
Low pass rate Fix SPF/DKIM first
Alignment failures Check domain matching
Reports not received Verify email addresses
Subdomains not covered Add sp= policy

Conclusion

Email authentication through SPF, DKIM, and DMARC provides essential protection against email-based attacks. By implementing these protocols, you protect your domain from spoofing, improve email deliverability, and build trust with recipients.

Key takeaways:

  1. Start with SPF: Authorize your sending servers
  2. Add DKIM: Cryptographically sign your emails
  3. Implement DMARC: Set policy and gain visibility
  4. Monitor reports: Use DMARC feedback to improve
  5. Roll out gradually: Start with monitoring, move to enforcement
  6. Maintain regularly: Update as sending sources change

By following this guide, you’ll have comprehensive email authentication that protects your domain and improves your email sender reputation.

Resources

Comments