DEV Community

Cover image for Why your phishing simulations land in spam (and the SPF / DKIM / DMARC fix that actually works)
David McHale
David McHale

Posted on

Why your phishing simulations land in spam (and the SPF / DKIM / DMARC fix that actually works)

Every security awareness program eventually has the same conversation:

"We sent the campaign yesterday. The dashboard says it went out. But
nobody clicked, and three people on Slack are asking why they didn't get
the test email."

Then somebody opens their spam folder and finds the simulated phish
sitting next to a Nigerian prince. The campaign isn't broken. The
deliverability is.

I've spent enough time debugging this for HailBytes SAT customers that I
can write the post-mortem from memory. Here it is.

The core problem

A phishing simulation is, by construction, an email designed to look
suspicious. Modern mail providers (Microsoft 365, Google Workspace,
Mimecast, Proofpoint) are trained on suspicious email and will quarantine
it aggressively unless you give them strong signals to trust the sender.

There are exactly four signals that matter at scale:

  1. SPF — does the sending IP have permission to send for this domain
  2. DKIM — is the message body cryptographically signed by the domain
  3. DMARC — what should receivers do if SPF/DKIM disagree
  4. Sender reputation — has this IP / domain pair sent good mail before

Get all four right and your campaigns hit the inbox. Miss any one and
you're fighting probabilities.

Step 1: do not send from your real corporate domain

This is the most common mistake. Teams deploy a phishing platform, point
it at noreply@theirrealcompany.com, and immediately damage their own
domain reputation when receivers flag the test as suspicious.

Use a separate purpose-built domain for simulated phishing.
security-training.example-corp.com or a fresh look-alike domain owned
by you. Publish DMARC on the real domain in p=reject. Run the sim
domain in p=none (or p=quarantine once warm) so receivers can
classify it as deliverable-but-suspicious — exactly the behaviour you
want for training.

Step 2: SPF that actually authorizes the sender

If you're running HailBytes SAT (or any platform) on your own EC2
instance and sending direct to MX, the SPF record on your sim domain
needs to authorize that IP:

v=spf1 ip4:203.0.113.42 -all
Enter fullscreen mode Exit fullscreen mode

If you're relaying through SES, SendGrid, or Postmark:

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

The two -all (hardfail) records will get you the strongest signal.
~all (softfail) is acceptable while warming up.

Step 3: DKIM, properly

DKIM is the part that breaks silently. Generate a 2048-bit key
(openssl genrsa -out dkim.key 2048) and publish the public half as a
TXT record at selector._domainkey.sim-domain.example.com. In SAT we
default to selector s1 — change it if you rotate keys.

The signature must cover at least From, Subject, Date, and
To. If your SMTP relay is rewriting headers, your signature will
break and DKIM will fail without an obvious error in the dashboard.

Verify with:

dig +short TXT s1._domainkey.sim-domain.example.com
Enter fullscreen mode Exit fullscreen mode

Then send yourself a test message and check Authentication-Results in
the raw headers. You want dkim=pass.

Step 4: DMARC alignment

DMARC requires that either SPF or DKIM passes and the authenticated
domain matches the From: domain. This trips up almost everyone who uses
a relay service.

If your From: is security@sim.example.com but DKIM is signed by
amazonses.com, DMARC alignment fails even though DKIM itself passes.

Fix: either sign with your own domain (best) or set the relay to use a
custom MAIL FROM that aligns with your domain (acceptable).

Publish DMARC at _dmarc.sim-domain.example.com:

v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com; pct=100
Enter fullscreen mode Exit fullscreen mode

The p=none is intentional during warming. Move to p=quarantine once
your reports show consistent SPF and DKIM passes.

Step 5: warm the IP

A brand-new IP sending 5,000 phishing simulations on day one will go
straight to spam, no matter how clean your DMARC is. Reputation is
volume-and-pattern based.

Realistic warming schedule for a new sim IP:

Day Volume Notes
1-3 50 Internal-only test accounts
4-7 200 Volunteer pilot group, mark "not spam" actively
8-14 500 First small department
15+ 2k+ Full org, monitor postmaster tools

Microsoft and Google both expose postmaster dashboards. Use them. If
you see reputation dropping to "low" or "bad" you're sending faster
than the receiver trusts.

Step 6: per-recipient header rules

Even with all of the above, individual mailboxes can have transport
rules that quarantine specific patterns. The single most useful thing
you can do as a security team is publish an internal allowlist
document
that mail admins can apply to bypass quarantine for the
sim domain only. Most receivers support per-domain or per-IP overrides.

A reasonable Microsoft 365 example: add the sim IP to the IP Allow
List in the connection filter, and create a transport rule that sets
SCL to -1 for messages from *@sim-domain.example.com. This bypasses
spam filtering but, importantly, not the user's perception — the
email still arrives looking suspicious, which is the whole point.

The result

When customers walk through this checklist with HailBytes SAT, inbox
placement on Microsoft 365 typically goes from 30-40% (campaign
launched cold) to 90%+ (campaign launched after a 2-week warm). The
single biggest jump comes from getting DKIM alignment right, not from
any IP-warming voodoo.

If you want to skip the homework, the platform handles SPF / DKIM /
DMARC scaffolding and IP warming as part of deployment — full guide at
hailbytes.com/sat. But honestly, the
above checklist is product-agnostic. Whatever sim platform you're
running, work through these six steps before you blame the tool.

Top comments (0)