rd containing a cryptographic token issued by your provider.
Architecture Decision: Use a dedicated subdomain prefix (e.g., _verify) to avoid polluting the root domain's TXT record space. This isolates verification from authentication and simplifies rotation.
_verify.example.dev. IN TXT "v=domain-verify; token=7a3f9c1d8e2b4f6a9c0d1e3f5a7b9c2d"
2. DKIM: Cryptographic Message Signing
DKIM attaches a digital signature to the message headers. The receiving server fetches your public key from DNS, validates the signature against the email content, and confirms the message wasn't altered in transit.
Architecture Decision: Use CNAME delegation instead of raw TXT records. CNAMEs point to ESP-managed endpoints, enabling automatic key rotation without manual DNS updates. Raw TXT records require manual regeneration every 6–12 months, increasing operational risk.
sel1._domainkey.example.dev. IN CNAME sel1.dkim.cloudmail.io.
sel2._domainkey.example.dev. IN CNAME sel2.dkim.cloudmail.io.
sel3._domainkey.example.dev. IN CNAME sel3.dkim.cloudmail.io.
All three selectors must resolve. Gmail and Microsoft heavily weight DKIM alignment in their spam scoring algorithms. Missing selectors degrade placement even if SPF passes.
3. SPF: Authorized Sender Enumeration
SPF is a TXT record at the root domain that enumerates permitted sending infrastructure. It uses the include: mechanism to delegate authorization to third-party services.
Architecture Decision: Start with ~all (soft fail). Hard fails (-all) will reject legitimate mail from misconfigured or newly added services. Soft fails flag messages for spam filtering while preserving delivery during transition.
example.dev. IN TXT "v=spf1 include:_spf.example.dev include:amazonses.com include:sendgrid.net ~all"
Critical Constraint: SPF evaluation halts after 10 DNS lookups. Each include: directive triggers a recursive resolution. If your chain exceeds 10, the record fails entirely. Use nested includes or consolidate services to stay within the limit.
4. Custom MAIL FROM Subdomain
The envelope sender (RFC 5321) is separate from the visible From: header (RFC 5322). By default, ESPs use their own domains as the bounce address, causing SPF alignment failures under DMARC. A custom subdomain routes bounces through your namespace.
Architecture Decision: Isolate bounce routing to a dedicated subdomain (e.g., bounce.example.dev). This requires two records: an MX record pointing to the ESP's feedback server, and a separate SPF record scoped to the subdomain.
bounce.example.dev. IN MX 10 feedback-smtp.us-east-1.amazonses.com.
bounce.example.dev. IN TXT "v=spf1 include:amazonses.com ~all"
This ensures the envelope sender aligns with your domain, satisfying DMARC's strict alignment requirements.
5. DMARC: Policy Enforcement & Reporting
DMARC sits atop SPF and DKIM. It defines how receivers handle authentication failures and provides aggregate/forensic reporting.
Architecture Decision: Deploy p=none initially. This mode collects data without enforcing rejection. Review reports for 2–4 weeks to identify legitimate senders, misconfigured integrations, or spoofing attempts before escalating to p=quarantine or p=reject.
_dmarc.example.dev. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-aggregate@example.dev; ruf=mailto:dmarc-forensic@example.dev; pct=100; adkim=s; aspf=s"
rua=: Aggregate reports (XML) sent daily.
ruf=: Forensic reports (full headers) sent on failure.
pct=100: Apply policy to 100% of mail.
adkim=s / aspf=s: Enforce strict alignment (exact domain match required).
Pitfall Guide
1. Hitting the SPF 10-DNS Lookup Limit
Explanation: SPF recursively resolves every include: directive. Marketing platforms, transactional ESPs, and HR systems often chain multiple includes. Exceeding 10 lookups causes immediate SPF failure.
Fix: Audit your chain with dig txt example.dev. Consolidate services where possible. Use include:_spf.example.dev to centralize third-party includes, or switch to DKIM-only alignment for non-critical senders.
2. Deploying -all Before Monitoring
Explanation: Hard fails reject unauthorized mail outright. If a legacy system, partner API, or misconfigured cron job sends from an unlisted IP, those messages vanish without trace.
Fix: Always start with ~all. Monitor DMARC reports for 14 days. Only transition to -all after confirming all legitimate sources are enumerated.
3. Forgetting the Subdomain SPF Record
Explanation: Many teams add the MX record for the bounce subdomain but omit the corresponding SPF TXT record. The envelope sender fails SPF evaluation, breaking DMARC alignment.
Fix: Always pair the bounce MX record with a scoped SPF record. Verify with dig txt bounce.example.dev.
4. Ignoring DMARC Aggregate Reports
Explanation: p=none generates XML reports containing every source claiming to send from your domain. Teams often deploy DMARC and never parse the reports, missing spoofing attempts or misconfigured internal tools.
Fix: Route rua= to a dedicated mailbox. Use automated parsers (e.g., Postmark DMARC, Agari, or open-source dmarc-report-parser) to convert XML into actionable dashboards.
5. Misaligning DKIM Selectors
Explanation: ESPs rotate keys periodically. If you hardcode TXT records instead of using CNAME delegation, expired keys cause signature validation failures.
Fix: Use CNAME delegation exclusively. Verify selector resolution with dig txt sel1._domainkey.example.dev. Ensure all three selectors provided by your ESP are published.
6. Overlooking DNS TTL Management
Explanation: High TTL values (e.g., 24h) delay propagation of record updates. During emergency key rotation or policy changes, stale DNS cache causes temporary delivery degradation.
Fix: Set TTL to 300–600 seconds during initial deployment. Once stable, increase to 3600 for production. Use TTL strategically to balance propagation speed and DNS query load.
7. Mixing Strict and Relaxed Alignment
Explanation: DMARC supports strict (s) and relaxed (r) alignment. Strict requires exact domain matches; relaxed allows subdomain matches. Mixing policies across services causes inconsistent evaluation.
Fix: Standardize on adkim=s and aspf=s for transactional mail. Use r only for marketing platforms that send from third-party subdomains you don't control.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Startup MVP / Low Volume | SPF + DKIM + p=none DMARC | Fast deployment, minimal overhead, sufficient for initial deliverability | Low |
| Enterprise Multi-ESP | Full Stack + Strict Alignment + Centralized SPF Include | Prevents lookup limit exhaustion, ensures consistent policy across services | Medium |
| High-Volume Transactional | Full Stack + p=reject DMARC + Forensic Reporting | Maximizes inbox placement, blocks spoofing, provides immediate failure visibility | Medium-High |
| Marketing / Third-Party Senders | DKIM + Relaxed DMARC Alignment | Accommodates subdomain sending without breaking delivery | Low |
Configuration Template
; Domain Ownership
_verify.example.dev. IN TXT "v=domain-verify; token=7a3f9c1d8e2b4f6a9c0d1e3f5a7b9c2d"
; DKIM Delegation (CNAME)
sel1._domainkey.example.dev. IN CNAME sel1.dkim.cloudmail.io.
sel2._domainkey.example.dev. IN CNAME sel2.dkim.cloudmail.io.
sel3._domainkey.example.dev. IN CNAME sel3.dkim.cloudmail.io.
; Root SPF
example.dev. IN TXT "v=spf1 include:_spf.example.dev include:amazonses.com include:sendgrid.net ~all"
; Centralized Include (Optional, reduces lookup count)
_spf.example.dev. IN TXT "v=spf1 include:postmarkapp.com include:mailgun.org ~all"
; Custom MAIL FROM Subdomain
bounce.example.dev. IN MX 10 feedback-smtp.us-east-1.amazonses.com.
bounce.example.dev. IN TXT "v=spf1 include:amazonses.com ~all"
; DMARC Policy
_dmarc.example.dev. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-aggregate@example.dev; ruf=mailto:dmarc-forensic@example.dev; pct=100; adkim=s; aspf=s"
Quick Start Guide
- Generate Verification Token: Request domain verification from your ESP. Add the
_verify TXT record to your DNS zone.
- Publish DKIM CNAMEs: Copy the three selector CNAMEs from your ESP dashboard. Verify resolution with
dig txt sel1._domainkey.example.dev.
- Deploy SPF & Bounce Subdomain: Add the root SPF record and configure the
bounce. subdomain with MX + scoped SPF. Test with dig mx bounce.example.dev.
- Activate DMARC Monitoring: Publish the
_dmarc TXT record with p=none. Route rua= to a dedicated mailbox or parser. Review reports after 7 days before escalating policy.