DEV Community

Battle Hardened
Battle Hardened

Posted on

Why Your Email is an Open Door for Spammers -- And How to Lock It

A practical guide to SPF, DKIM, DMARC, and Sieve filtering for anyone who owns a domain


The Problem Nobody Fixes

Everyone complains about spam. It clogs inboxes, wastes time, and tricks enough people often enough that it remains wildly profitable for the people sending it. The numbers game works: send a billion emails, fool 0.01% of recipients, and you have 100,000 victims.

Here's the uncomfortable truth: we have the tools to make a significant dent in this problem. They've existed for years. They're not complicated to implement. And the vast majority of domain owners — including many who absolutely should know better — haven't bothered.

This isn't a technical limitation. It's a collective choice.

This article explains what those tools are, why they work, why they're underused, and exactly how to configure them correctly. We'll walk through real-world examples using Cloudflare for DNS, then generalize to any provider.

Scope: this article is written primarily for personal domains and small deployments using a single outbound mail provider. Large enterprises and multi-sender environments have additional operational constraints — those are acknowledged throughout, but the primary audience is the domain owner running a single inbox who has no good excuse for weak defaults.


What Authentication Actually Does (And Doesn't Do)

Before diving in, it's important to set expectations. Email authentication is not a spam filter. It does one specific thing: it proves that mail claiming to come from your domain actually came from your domain.

That matters because a huge category of spam and phishing relies on spoofing — forging the sender address to make an email look like it came from someone trustworthy. Authentication closes that door.

It does not stop a spammer who registers their own domain, sets up authentication correctly, and sends spam from there. A perfectly authenticated spam email is still spam. Content filtering handles that problem — and we'll get to that too.


If You Only Read One Section

Three DNS records. That's the entire deployment. Everything else in this article explains why they matter and how to get them right.

Record Type What it does
SPF with -all TXT at @ Tells receivers which servers are authorized to send as your domain
DKIM TXT at selector._domainkey Cryptographically signs outgoing mail so tampering is detectable
DMARC p=reject TXT at _dmarc Publishes your enforcement policy and ties SPF/DKIM to your visible From address

Verify with mail-tester.com after deploying. All three passing means you're done.

Provider-specific setup guides for Google Workspace, Microsoft 365, Proton Mail, GoDaddy, Amazon SES, and Cloudflare are in the "Provider Setup Guides" section below. The rest of this article explains the why, the caveats, and the inbound filtering layer for those who want to go further.


The Three Pillars

SPF — Sender Policy Framework

SPF lets you publish a list of servers authorized to send mail from your domain. When a receiving mail server gets a message claiming to be from you, it checks your DNS records to see if the sending server is on your approved list.

SPF lives in a DNS TXT record at your root domain and looks like this:

v=spf1 include:_spf.example.com -all
Enter fullscreen mode Exit fullscreen mode

The critical part is the end: -all means mail from unauthorized senders produces an SPF Fail result. Most receivers treat this as a strong rejection signal — but it's the receiver's policy, not SPF itself, that determines what happens next. In modern mail systems, SPF failures become significantly more meaningful when paired with DMARC alignment and enforcement — SPF alone is not sufficient to prevent spoofing of your visible From address. By contrast, ~all — softfail — signals "be suspicious but deliver it anyway." That's a weak default that essentially tells receivers the rule is optional.

For a personal domain or a single-provider setup, -all should usually be your goal. Large providers and enterprises often default to ~all for reasons beyond ignorance — support costs from broken outbound mail are real, and permissive authentication is operationally safer when you're not certain what's sending on your behalf. That's a defensible posture during discovery. It should be a waypoint, not a destination. On a domain you run out of a single inbox, you don't have that excuse.

The argument against hardfail is that mail forwarding breaks SPF — when someone forwards your mail, the forwarding server's IP isn't on your approved list. This is true. But it's not a reason to use softfail indefinitely; it's a reason to also configure DKIM and DMARC, which handle forwarding correctly.

Common SPF mistakes:

  • Using ~all (softfail) instead of -all (hardfail)
  • Using mx to authorize senders — operationally convenient for simple setups, but it couples outbound authorization to inbound routing. If your MX records change, your SPF authorization changes too, sometimes unintentionally
  • Exceeding the 10 DNS lookup limit by including too many third-party senders

DKIM — DomainKeys Identified Mail

DKIM adds a cryptographic signature to every outgoing message. The receiving server looks up your public key in DNS and uses it to verify the signature. If the message was tampered with in transit, the signature breaks.

More importantly for forwarding: where SPF fails on forwarded mail, DKIM generally passes — because the signature travels with the message. It isn't a 100% guarantee. Forwarders that aggressively rewrite subjects or inject security banners into the message body will still break the signature. But DKIM remains the primary safety net that makes a strict DMARC policy operationally viable, and modern forwarding infrastructure preserves it far better than older list software did.

Modern large providers increasingly use ARC (Authenticated Received Chain) to preserve authentication context across forwarding hops and mailing lists. ARC doesn't replace DKIM or DMARC — it adds a chain of custody that lets receivers trust authentication results that would otherwise appear broken after redistribution. It's worth knowing the term exists if you're debugging forwarding failures, though for most personal domain operators it's infrastructure that works in the background rather than something you configure directly.

DKIM is configured in two places:

  1. Your mail server generates a key pair and signs outgoing messages with the private key
  2. You publish the public key in DNS as a TXT record under a selector subdomain, like cf2024-1._domainkey.yourdomain.com

Your mail provider handles the signing. Your job is publishing the DNS record they give you.

DMARC — Domain-based Message Authentication, Reporting and Conformance

DMARC is the piece that ties everything together and actually gives you enforcement.

Without DMARC, receivers can see your SPF and DKIM results but have no instruction from you about what to do when they fail. DMARC lets you publish that policy.

More importantly, DMARC checks alignment — it verifies that the authenticated domain matches the visible From: header that recipients actually see. SPF alone authenticates the envelope sender (the technical bounce address), which can differ from the From header. DMARC closes that gap.

DMARC lives in a DNS TXT record at _dmarc.example.com:

v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r;
Enter fullscreen mode Exit fullscreen mode

Breaking that down:

  • p=reject — requests that receivers reject mail failing DMARC alignment
  • sp=reject — apply the same policy to subdomains
  • adkim=r — relaxed DKIM alignment: the signing domain must match or be a subdomain of your From domain
  • aspf=r — relaxed SPF alignment: same requirement for SPF

A critical clarification on forwarding: DMARC only requires either SPF or DKIM to align — not both. When mail is forwarded, SPF breaks because the forwarding server's IP isn't in your SPF record. But DKIM signatures travel with the message and survive forwarding intact. Because DKIM alignment still passes, DMARC passes too. This is the key reason p=reject doesn't destroy forwarded mail — DKIM is the safety net.

A note on alignment modes: The ecosystem default is relaxed alignment (adkim=r; aspf=r), which allows subdomains to match — so mail.yourdomain.com aligns with yourdomain.com. Changing this to strict (adkim=s; aspf=s) is a deliberate hardening step. Only do it if you are certain your provider isn't routing outbound mail through a tracking or bounce subdomain. Strict alignment is where many real-world deployments break unexpectedly — ESPs, marketing platforms, bounce-handling infrastructure, and Salesforce-style senders all commonly use subdomains that require relaxed to function.

The policy ladder:

  • p=none — monitoring only, no enforcement. Useful temporarily when first setting up.
  • p=quarantine — send failures to spam. A reasonable middle step.
  • p=reject — requests that receivers reject mail failing DMARC alignment. This is the goal. Most major receivers honor it; a small number apply local overrides.

Most domains that have DMARC at all are sitting on p=none — which means they've done the paperwork but none of the work. It's the equivalent of posting a "no trespassing" sign with no fence.


Your Three Records — Right Now

This is the complete configuration. Three DNS records. Everything else in this article is the why; this is the what.


The Records

SPF — TXT record at @ (your root domain)

v=spf1 include:_spf.example.com -all
Enter fullscreen mode Exit fullscreen mode

Replace the include with what your mail provider specifies. The -all at the end is yours to set — use it regardless of what your provider recommends.

DKIM — TXT record at selector._domainkey.yourdomain.com

v=DKIM1; h=sha256; k=rsa; p=[key your provider gives you]
Enter fullscreen mode Exit fullscreen mode

Your provider generates this. Copy it exactly. Don't type it manually.

DMARC — TXT record at _dmarc.yourdomain.com

v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r;
Enter fullscreen mode Exit fullscreen mode

This is the enforcement record. p=reject is the goal — not p=none, not p=quarantine. Relaxed alignment (r) is the safe default and works for most setups. If you are certain your provider sends from your exact root domain — not a subdomain — you can harden further with adkim=s; aspf=s.


Provider Setup Guides

Your mail provider gives you the SPF include string and DKIM key. Here's where to find them:

Provider Setup Documentation
Google Workspace Admin Console → Apps → Gmail → Authenticate Email
Microsoft 365 Security Portal → Email Auth Settings
Proton Mail Settings → Domain Names → Review
GoDaddy Email Email & Office Dashboard → Manage
Amazon SES SES Console → Identities → Easy DKIM
Cloudflare + any provider Add records in DNS dashboard → DNS → Add Record

Prerequisites before adding DMARC:

  • SPF record must exist and be correct
  • DKIM must be active and signing outbound mail
  • Verify SPF and DKIM are passing on real outbound mail before enforcing DMARC — send a test message and check the headers or use mail-tester.com to confirm both are working

Verify It Worked

Once records propagate — usually minutes to an hour, though some providers cache aggressively and full propagation can occasionally take several hours depending on TTLs and recursive resolver behavior:

All green means you're done. One additional step worth taking: send a live email to a personal Gmail or Outlook account and check the raw headers ("View Original" in Gmail, "View Message Source" in Outlook). Confirm dmarc=pass appears and that the alignment fields match your domain. mail-tester.com is an isolated test environment — real production alignment issues occasionally only surface in actual delivery.


Receive-Only Domains

If a domain only receives mail and never sends, it's even simpler — no DKIM needed:

SPF:   v=spf1 -all
DMARC: v=DMARC1; p=reject; sp=reject;
Enter fullscreen mode Exit fullscreen mode

Why Most Domains Don't Do This

This is worth understanding, because the technical setup is genuinely not hard.

The defaults are wrong. Most mail providers auto-generate an SPF record with ~all. Most DNS providers don't prompt you to add a DMARC record. Most registrars don't mention authentication during domain setup. The path of least resistance leads to a weak configuration.

The coordination problem is real but overused as an excuse. The argument goes: if I enforce strictly, I might break legitimate forwarded mail, so I'll wait for everyone else to upgrade first. But everyone waits. The result is collective inaction dressed up as prudence.

The pain falls on others. If your domain gets spoofed in a phishing campaign, the victims suffer — not you. The cost of doing nothing is externalized. That's a broken incentive structure, and it's why voluntary adoption has been slow.

The big players have leverage they haven't fully used. Google and Microsoft together handle a majority of the world's email. Their 2024 mandate requiring DKIM and DMARC for bulk senders moved more domains into compliance than a decade of documentation. But they stopped at p=none — monitoring only. Extending that requirement to enforcement would change the ecosystem overnight.


A Real-World Configuration: Cloudflare

Here's what a correct, strongly enforced configuration looks like for a domain using Cloudflare for DNS. The mail provider is irrelevant — these records are the same regardless of who hosts your mail.

DNS Records Required

SPF (TXT record at @):

v=spf1 include:_spf.example.com -all
Enter fullscreen mode Exit fullscreen mode

Your mail provider's documentation will give you the correct include: value. Whatever they recommend for the rest of the record, the -all at the end is your decision — use it.

DKIM (TXT record — your provider gives you this):

selector._domainkey.yourdomain.com  →  v=DKIM1; h=sha256; k=rsa; p=[your public key]
Enter fullscreen mode Exit fullscreen mode

Your provider generates and manages your DKIM key. They'll give you the exact record to add. Copy it exactly as provided.

DMARC (TXT record at _dmarc):

v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r;
Enter fullscreen mode Exit fullscreen mode

A Note on DMARC Reporting

DMARC supports rua and ruf tags for aggregate and forensic reports:

v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r; rua=mailto:dmarc@yourdomain.com;
Enter fullscreen mode Exit fullscreen mode

These reports arrive as zipped XML files — a format that is, charitably, not user-friendly. Two types exist: aggregate (rua) and forensic (ruf). Aggregate reports are the operationally important ones — they show who is sending as your domain and whether legitimate mail is failing alignment. Forensic reports were designed to provide per-message failure detail, but many receivers have stopped sending them due to privacy concerns, and many providers ignore them entirely. Don't worry if forensic reports never arrive. Focus on aggregate. Services like Postmark's free DMARC monitoring tool or Dmarcian parse and visualize aggregate reports without requiring you to touch XML. If you won't monitor them during rollout or troubleshooting, they quickly become noise — but during initial setup, they're worth having.

What This Looks Like in Cloudflare

In Cloudflare's DNS dashboard, your mail-related records should look like this:

Type Name Content
MX @ mail.example.com
TXT @ v=spf1 include:_spf.example.com -all
TXT selector._domainkey v=DKIM1; h=sha256; k=rsa; p=[key]
TXT _dmarc v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r;

A word on Cloudflare: They cannot safely force aggressive enforcement defaults without breaking legacy setups — scanner-to-email appliances, forgotten SaaS integrations, automated billing systems. But they could implement proactive validation wizards that nudge users away from abandoned p=none policies and warn about broken SPF topologies. That gap between what they do and what they could do is a product decision, not a technical limitation. If you use Cloudflare, it's worth providing feedback directly.


Advanced filtering ahead. Everything above this point is baseline domain security — SPF, DKIM, and DMARC apply to any domain owner regardless of mail client or provider. Everything below is optional advanced filtering for operators whose mail host exposes Sieve scripting. If your provider doesn't support Sieve, or you're not comfortable editing server-side scripts, the authentication stack above is sufficient.


Server-Side Spam Filtering with Sieve

Authentication hardens your outbound identity. For inbound spam that passes authentication — because the spammer owns a legitimate domain — you need filtering.

Sieve is a server-side mail filtering language supported by many mail hosts. It runs before mail reaches your device, which means filtering happens regardless of which client you use. Check your mail provider's documentation or support to see if they offer Sieve scripting — it is often available but not prominently advertised.

Before deploying any discard rules: Start by routing suspect messages to Junk with fileinto "Junk" instead of discarding them. Run that way for several days and confirm no legitimate mail is being caught. Only switch to discard once you're confident the rules aren't silently blackholing invoices from a broken vendor or notifications from a system that signs its mail poorly. discard is irreversible — the sender gets no bounce, you get no copy, and there's no way to recover the message.

A practical Sieve script for aggressive inbound filtering:

require ["body", "fileinto", "regex"];

# Discard mail flagged by server spam detection
# X-Junk-Flag is applied probabilistically by your mail host — false positives
# on financial mail or invoices are possible. Start with fileinto "Junk" here
# too until you've audited your environment for at least a week.
if allof (header :is "x-junk-flag" "YES")
{
    discard;
}

# Discard mail with no DKIM signature that also fails DMARC
if allof (
    header :contains "Authentication-Results" "dkim=none",
    header :contains "Authentication-Results" "dmarc=fail"
)
{
    discard;
}

# Discard mail with a broken DKIM signature
# dkim=fail means DKIM was attempted but the signature didn't verify —
# the message body was modified after signing. Legitimate mail rarely
# does this. Start with fileinto "Junk" to review before switching to discard.
if header :contains "Authentication-Results" "dkim=fail"
{
    discard;
}

# Discard mail with high spam scores (Rspamd >= 6.0)
# Regex matches any positive score of 6.0 or above (single or double digit).
# The ^ anchor ensures negative scores like -6.5 never match.
if header :regex "X-Rspamd-Score" "^([6-9]|[1-9][0-9])\."
{
    discard;
}
Enter fullscreen mode Exit fullscreen mode

Inbound Filtering: The Spam Score Dilemma

Filtering on X-Rspamd-Score exposes a gap in Sieve's standard tooling: the protocol lacks a native, universally supported way to handle decimal and negative numbers safely. Because highly trusted mail can receive a negative score (e.g., -2.1), how you write this rule requires an engineering trade-off between precision, portability, and predictability. Here are the three approaches, ordered by preference for predictability over syntax elegance:

Tier 1 — Regex (precise and robust): The script above. The ^ anchor ensures negative scores like -6.5 never match. Single clean rule, no edge cases.

  • Pro: Highly precise. Explicitly avoids negative numbers via string positioning anchors.
  • Con: The regex extension is optional and not universally supported by all mail hosts.

Tier 2 — String matching (highly portable, safe fallback): Works on every Sieve implementation without extensions. Negative scores never match because - doesn't appear in any of the patterns. Verbose but deterministic.

if anyof (
    header :contains "X-Rspamd-Score" "6.",
    header :contains "X-Rspamd-Score" "7.",
    header :contains "X-Rspamd-Score" "8.",
    header :contains "X-Rspamd-Score" "9.",
    header :contains "X-Rspamd-Score" "10.",
    header :contains "X-Rspamd-Score" "11.",
    header :contains "X-Rspamd-Score" "12.",
    header :contains "X-Rspamd-Score" "13.",
    header :contains "X-Rspamd-Score" "14.",
    header :contains "X-Rspamd-Score" "15."
)
{
    discard;
}
Enter fullscreen mode Exit fullscreen mode
  • Pro: Universal — works on every Sieve engine without extensions. Safe from negative score edge cases.
  • Con: Verbose and repetitive. Prioritizes deterministic behavior over code elegance. Double-digit scores above 15 require additional entries (uncommon in practice).

Tier 3 — Relational extension (unpredictable; use with caution): The relational extension with i;ascii-numeric handles integer comparison cleanly for positive numbers. The RFC does not officially support negative numbers and leaves behavior with unparseable input implementation-defined.

require ["relational", "comparator-i;ascii-numeric"];

# Use with caution: behavior with negative Rspamd scores is engine-dependent
if header :value "ge" :comparator "i;ascii-numeric" "X-Rspamd-Score" "6"
{
    discard;
}
Enter fullscreen mode Exit fullscreen mode
  • Pro: Clean single-line numeric comparison for positive scores.
  • Con: When encountering a negative score like -1.5, behavior varies by implementation — some servers evaluate it as 0, others skip the comparison, others may log an error. The worst-case outcome is that a negative-scored message fails to match and lands in your inbox, which is correct behavior for clean mail anyway. The real risk is that this script may behave differently if you ever migrate mail providers.

Recommendation: Try Tier 1 first. If your provider doesn't support regex, use Tier 2. Reserve Tier 3 only if neither of the above works, and document why.

Why discard instead of moving to Junk?

discard silently drops the message with no response to the sender. This is preferable to bouncing or junking for two reasons:

  1. Any response — even a bounce — confirms your address exists, which is valuable to spammers sending speculatively
  2. Spam often uses forged return addresses, so a bounce lands in an innocent person's inbox, making you a source of collateral spam

Understanding DKIM results: There are three distinct outcomes worth knowing:

  • dkim=none — the message carries no DKIM signature at all. Combined with a DMARC fail, this is a strong spam signal.
  • dkim=fail — DKIM was attempted but the signature didn't verify. This means the message body was modified after signing. Things that legitimately break DKIM include old mailing list software, enterprise security gateways that append disclaimers, "safe links" URL rewriting, and MIME normalization bugs. For a personal domain not participating in legacy mailing lists or enterprise forwarding chains, dkim=fail is a strong spam signal.
  • dkim=pass — the signature verified correctly. This proves the message wasn't tampered with in transit, but says nothing about whether the content is spam.

Rollout recommendation for dkim=fail: Before setting it to discard, run it as fileinto "Junk" for a few days to confirm no legitimate mail is being caught. Once satisfied, switch to discard.

A note on header portability: The Sieve rules above match against Authentication-Results header strings as they appear in real headers from a specific mail host. Different providers format these headers differently — some normalize them, some rewrite them, some strip upstream authentication results entirely. Before deploying any of these rules, send yourself a few test messages and inspect the raw headers your provider actually produces. The string patterns in your Sieve script need to match what your provider writes, not what the RFC says they should write.

A note on Rspamd score thresholds: The threshold of 6.0 is a reasonable starting point. Monitor for false positives and adjust up or down based on what you see. Scores vary by provider — what Rspamd considers a 6 on one system may differ slightly from another.

What Sieve won't catch: Sophisticated spam from authenticated domains with low spam scores. This is the hardest category — mail that passes every technical check because the sender set everything up correctly and keeps content just below detection thresholds. Apple Mail, Outlook, and dedicated tools like SpamSieve use content analysis and learned patterns to catch these. Sieve and client-side filtering work best as complementary layers.


When Softer Settings Are Legitimate

This article advocates for the strongest settings — SPF with -all enforcement semantics (commonly called hardfail), strict DMARC enforcement, DKIM signing. For most home users and hobbyists with a single mail provider, those settings are correct and there is no good reason not to use them.

There are, however, legitimate scenarios where softer settings reflect a genuine operational constraint rather than inaction:

Mailing lists and list software. Software like Mailman often rewrites message headers or strips DKIM signatures when redistributing mail to subscribers. This breaks DMARC alignment. Organizations running high-volume mailing lists sometimes stay on p=quarantine or p=none to avoid breaking list delivery for their users.

Complex forwarding infrastructure. If your domain uses a third-party forwarding service to redirect mail to another address, the forwarding server's IP won't be in your SPF record, and DKIM may not survive the forward intact. This is a real constraint that requires careful handling before moving to strict enforcement.

Large enterprises with many third-party senders. A company using Salesforce, Marketo, a ticketing system, a payment processor, and an HR platform — all sending mail as their domain — needs to audit every one of those senders before enforcing DMARC. Moving to p=reject before that audit is complete means silently dropping legitimate business mail. p=none during that audit period is the correct approach; the problem is that the audit period often becomes permanent.

Domains mid-migration. p=none is the right starting point when you genuinely don't know all your sending sources. The DMARC policy ladder — none → quarantine → reject — exists for a reason. The mistake is treating p=none as a destination rather than a waypoint.

None of these apply to a home user or hobbyist running a single domain with one mail provider. If you send mail through exactly one service and receive reports that everything is working, there is no technical justification for softfail SPF or p=none DMARC. The exceptions are real, but they belong to a specific category of organizational complexity that most readers of this article don't have.


The Ceiling

With SPF -all, DKIM, DMARC p=reject, and Sieve filtering in place, you've hit the ceiling of what the email authentication protocol allows. You will still receive some spam — the sophisticated kind, from properly authenticated domains with low spam scores. That's not a failure of your configuration; it's the unsolved remainder of a problem the entire industry is still working on.

What you've done is:

  • Prevented straightforward spoofing of your domain at receivers that honor DMARC enforcement
  • Discarded the majority of inbound spam before it reaches your device
  • Configured your domain better than most commercial organizations bother to

The configuration in this article was applied to a real personal domain in a single afternoon. Inbound spam dropped by over 160 messages per day — and that's counting only what reached the server. An unknown larger number was discarded silently before it ever arrived. The spam that still gets through is the sophisticated, fully authenticated kind that the entire industry hasn't solved yet. It goes to Junk. It doesn't reach the inbox.

The tools exist. The setup takes an afternoon. The frustrating part is that this works remarkably well for preventing domain spoofing and cutting down low-quality inbound spam — and most people still won't do it.

Top comments (0)