The problem: anyone can claim to be you
SMTP — the protocol that moves email between servers — was designed in 1982 and has no built-in identity check. Any server on the internet can connect to your customer's mail server and declare it is sending on behalf of twenty-four.it. Nothing in the protocol stops it.
What makes this worse is that every email actually carries two different sender identities, and most people only ever see one of them:
✉️ The envelope — RFC 5321 (SMTP)
Used by servers for routing and bounces. Thrown away on delivery — the user never sees it. The receiving server saves the envelope sender into a hidden Return-Path: header.
📄 The letter inside — RFC 5322 (the message)
The headers and body — this is all Outlook shows. The From: here is just text. It does not have to match the envelope.
The three checks — a postal analogy
SPF — the approved courier list
The domain publishes, in DNS, a list of servers allowed to hand over its envelopes. The receiver checks: did this envelope arrive via an approved courier (IP address)?
Checks the envelope sender + connecting IP. Knows nothing about the letter inside.
DKIM — the wax seal
The sending server stamps the letter with a cryptographic seal only it can make. The receiver fetches the matching public key from DNS and verifies the seal is genuine and the letter wasn't altered in transit.
Proves integrity + which domain signed it. Survives forwarding.
DMARC — the policy card
The domain's instruction to every receiver: "If neither check lines up with the From address on the letterhead — deliver anyway / junk it / reject it. And send me a report either way."
Adds alignment, policy and reporting on top of SPF + DKIM.
SPF — Sender Policy Framework
A public list, published in DNS, of every server allowed to put your domain on the envelope. The receiving server looks at the connecting IP address and the domain in MAIL FROM, fetches that domain's SPF record, and checks whether the IP is on the list.
The record — click each part
👆 Click any part of the record to see what it does.
Try it: which senders pass?
The receiver walks the record left to right and stops at the first mechanism that matches the connecting IP. Pick a sender and run the check:
Qualifiers — the character in front of a mechanism
| Qualifier | Result | Receiver's usual treatment |
|---|---|---|
| + (default) | pass | Sender is authorised. ip4:… means +ip4:… |
| -all | fail | Hard fail — "anyone else is not us". Strongest signal; what we deploy. |
| ~all | softfail | "Probably not us" — usually accepted but spam-scored. Useful mid-rollout. |
| ?all | neutral | No opinion — near useless in production. |
What engineers get wrong about SPF
DKIM — DomainKeys Identified Mail
A tamper-evident cryptographic seal. The sending server holds a private key and signs each outgoing message; the matching public key sits in DNS for anyone to fetch. If the signature verifies, the receiver knows two things: the message really was handled by the domain in d=, and the signed content hasn't changed one byte since signing.
1 · Sign at source
Exchange Online canonicalises the message, hashes the body, then signs the chosen headers + body hash with the domain's private key. The result is added as a DKIM-Signature: header.
2 · Key in DNS
The public key is published at <selector>._domainkey.<domain>. The selector in the signature tells the receiver exactly which record to fetch — so a domain can hold several keys at once.
3 · Verify at destination
The receiver re-hashes what arrived and checks the signature with the public key. Any edit to the signed headers or body — even one character — and verification fails.
The signature the sender adds — click each tag
👆 Click a tag — d= and s= are the two that matter most day-to-day.
The public key the receiver fetches — click each part
👆 Click any part of the record.
Tamper test — why the seal works
A signed message is in transit. Deliver it untouched, or let an attacker-in-the-middle edit the body, and watch the body hash comparison:
Subject: July invoice
DKIM-Signature: d=twenty-four.it; s=selector1; bh=…; b=…
What engineers get wrong about DKIM
DMARC — Domain-based Message Authentication, Reporting & Conformance
SPF checks the envelope. DKIM checks the seal. Neither looks at the From address the user reads. DMARC is the policy layer that closes that gap: it demands the passing check lines up with the visible From domain, tells receivers what to do when it doesn't, and sends you reports about everyone sending as your domain.
The record — click each tag
👆 Click a tag. p= is the one that changes the world.
Alignment lab — the concept that makes DMARC click
The message the user sees says From: mark@twenty-four.it. Set what actually happened underneath, and see the DMARC verdict:
The policy ladder — how we roll DMARC out
p=none
Monitor. Deliver as normal, but send aggregate reports. Weeks 1–4: discover every legitimate system sending as the domain (CRM, payroll, copiers, ticketing…) and fix their SPF/DKIM.
p=quarantine
Junk it. Failing mail goes to spam. Ramp with pct=25 → 100 once reports show legitimate sources are aligned.
p=reject
Refuse it. Failing mail is bounced at SMTP — the destination-side goal. Spoofed invoices never reach a human. Keep watching reports for new senders.
Reports — the R in DMARC
Every major receiver (Microsoft, Google, Yahoo…) sends a daily aggregate report (gzipped XML) to your rua= address: which IPs sent as your domain, volumes, and SPF/DKIM/alignment results per source. Raw XML is painful — in practice we point rua at an analyser (e.g. a DMARC reporting service) and read dashboards.
<!-- one row of an aggregate report: someone in 198.51.100.0/24 spoofed us 37 times -->
<record>
<row><source_ip>198.51.100.66</source_ip><count>37</count>
<policy_evaluated><disposition>reject</disposition>
<dkim>fail</dkim><spf>fail</spf></policy_evaluated></row>
<identifiers><header_from>twenty-four.it</header_from></identifiers>
</record>
What DMARC can and cannot do
The Mail Journey — sender ⇄ DNS ⇄ recipient
This is the whole story end-to-end: Mark emails sarah@contoso.com. Watch the SMTP conversation, every DNS lookup, and each check as the recipient server makes its decision. Then change the scenario — spoof it, forward it, misconfigure it — and change our published DMARC policy to see how the outcome shifts.
TwentyFour IT — sender
🌐 Public DNS
Contoso Ltd — recipient
SMTP conversation & events
What's happening & why
Press Play or Step
The simulator will walk through every stage: composing, DKIM signing at source, the MX lookup, the SMTP handshake, then the recipient's SPF, DKIM and DMARC checks and the final verdict.
Tip: run the ✅ legitimate scenario first, then the 🕵️ clever spoof — the contrast between them is the reason DMARC exists.
Knowledge check
Eight questions, the kind that come up on real tickets. Answer honestly — explanations appear as you go.
Engineer's cheat sheet
The three records at a glance
| Lives at (DNS) | Looks like | Protects / provides | |
|---|---|---|---|
| SPF | twenty-four.it (TXT) | v=spf1 ip4:203.0.113.10 include:spf.protection.outlook.com -all | Which IPs may use the domain on the envelope (MAIL FROM) |
| DKIM | selector1._domainkey.twenty-four.it (TXT / CNAME for M365) | v=DKIM1; k=rsa; p=<public key> | Message integrity + proof the d= domain signed it |
| DMARC | _dmarc.twenty-four.it (TXT) | v=DMARC1; p=reject; rua=mailto:dmarc@twenty-four.it | Alignment with the visible From + policy + reports |
Look them up (PowerShell / dig)
# PowerShell (any Windows box) Resolve-DnsName twenty-four.it -Type TXT | Select -Expand Strings # SPF Resolve-DnsName _dmarc.twenty-four.it -Type TXT # DMARC Resolve-DnsName selector1._domainkey.twenty-four.it -Type TXT # DKIM (follows the M365 CNAME) # dig equivalents dig +short TXT twenty-four.it dig +short TXT _dmarc.twenty-four.it dig +short TXT selector1._domainkey.twenty-four.it
Read the receiver's verdict on any message
Open the message headers (Outlook: File → Properties → Internet headers, or the Message Header Analyzer add-in / mha.azurewebsites.net) and find Authentication-Results: — the receiving server writes its SPF/DKIM/DMARC results there:
Authentication-Results: spf=pass (sender IP is 40.107.22.75) smtp.mailfrom=twenty-four.it; dkim=pass (signature was verified) header.d=twenty-four.it; dmarc=pass action=none header.from=twenty-four.it
Also useful: Received-SPF: and Return-Path: (the saved envelope sender — compare it with From: to spot alignment issues by eye).
Ticket triage — symptom → first thing to check
| Symptom | Likely cause | Check / fix |
|---|---|---|
| Customer's outbound lands in recipients' junk | SPF/DKIM/DMARC missing or failing; new sender not in SPF | Authentication-Results on a caught message; verify all three records; align the sending service |
| Mail from a new platform (CRM, mailshot, alerts) bounced with 550 5.7.509 … does not pass DMARC | Platform sends as the domain but isn't authorised | Add their include to SPF and/or (better) set up their DKIM signing as the domain; test before go-live |
| Forwarded mail failing / quarantined at final hop | SPF always breaks on forward; DKIM must carry the pass | Confirm DKIM is enabled and intact; that's the aligned pass DMARC needs |
| "Too many DNS lookups" / SPF permerror in headers | >10 lookups from stacked includes | Count lookups; remove dead includes; consider consolidating senders |
| Intermittent SPF fail from one site only | Device relaying direct-to-internet (MFD/scanner, app server) with IP not in SPF | Route it via M365 SMTP relay/connector or add its egress IP |
| Spoof reported by a user but DMARC "passes" | Lookalike domain or compromised genuine mailbox | Check the exact From domain character-by-character; check sign-in logs / audit for compromise |
Rollout runbook (new customer domain)
| Step | Action |
|---|---|
| 1 | Inventory every legitimate sender of the domain: M365, printers/MFDs, apps, CRM, payroll, mailshot tools, website forms. |
| 2 | Publish/repair SPF (one record, all legit sources, aim for -all, stay under 10 lookups). |
| 3 | Enable DKIM everywhere possible — M365 selectors via Defender portal + each third-party platform signing as the domain. |
| 4 | Publish DMARC p=none with rua= to the reporting mailbox/analyser. Wait 2–4 weeks. |
| 5 | Fix every legitimate source the reports expose. Then p=quarantine (optionally pct= ramp). |
| 6 | Move to p=reject. Keep reading reports — new senders appear whenever someone buys new software. |