The message left your server, was accepted for evaluation, and was then refused on policy grounds. Nothing is wrong with the recipient address and nothing is wrong with DNS routing — if either were, you would be seeing a 5.1.x mailbox error or a connection failure instead. Something about who the receiver believes you are did not satisfy their inbound policy.
How should you read the status code?
SMTP replies carry two codes. The 550 is the basic reply code: permanent failure. The 5.7.15 is an enhanced status code from RFC 3463, where the middle digit is the subject class — 7 means "Security or Policy Status". Everything in 5.7.x is the receiver saying "I could have delivered this, and chose not to."
Microsoft-hosted receivers are the ones most people meet this from, and they publish a set of "Access denied" sub-codes for different causes — DMARC failures, banned sending IPs, reverse-DNS problems, spam-abuse blocks, and unauthenticated relay attempts each get their own. Which number maps to which reason is documented by Microsoft and does change, so check their current non-delivery report reference rather than a number you remember. What does not change is the family: authentication and reputation.
Get the full bounce first
Before touching DNS, get the raw text. A user forwarding you "it says access denied" is not enough — you need the sending IP, the recipient domain, the timestamp, and the complete remote response. On a Postfix relay:
# Pull the full remote response out of the mail log
grep -i "5.7.15" /var/log/mail.log | tail -20
# Follow one message end to end by queue ID
grep "A1B2C3D4E5" /var/log/mail.log
# Exim
exigrep "5.7.15" /var/log/exim4/mainlog | tail -20If the mail went out through a hosted platform, the equivalent is the platform's message trace or suppression log. In Exchange Online that is the message trace; in most ESPs it is the event log for that message ID. You are looking for the same three facts: which IP connected, which domain it authenticated as, and what the receiver said back.
Run the checks in this order
Work outside in. Each of these is cheap to test and each one rules out a class of cause, so resist the urge to jump to "we must be blacklisted" before the authentication basics are confirmed.
1. SPF — does it pass, and for the right domain?
SPF is checked against the envelope sender (MAIL FROM / Return-Path), not the visible From: header. Confirm the record exists, returns exactly one v=spf1 string, stays inside the ten DNS-lookup budget, and authorizes the IP that actually connected.
# One record, and only one
dig +short TXT example.com | grep spf1
dig +short TXT example.com | grep -c spf1
# What the vendor include really authorizes
dig +short TXT _spf.google.comA permerror here is silently fatal: the record looks fine in the zone but evaluates to "not a pass" everywhere. SPF PermError: too many DNS lookups covers the counting rules, and the SPF checker resolves the live chain for you.
2. DKIM — is the message signed, and does the signature verify?
DKIM is the more robust of the two, because it survives forwarding. Find the selector from a real message's DKIM-Signature header (the s= tag) and confirm the public key is published:
# Replace SELECTOR with the s= value from the DKIM-Signature header
dig +short TXT SELECTOR._domainkey.example.com
# Google Workspace default selector
dig +short TXT google._domainkey.example.comAn empty result means the key was never published, was published at the wrong name, or was removed during a DNS migration. If the key resolves but signatures still fail, the message is being modified in transit — see dkim=fail "body hash did not verify". The DKIM checker will discover common selectors for you if you do not have a sample message.
3. DMARC alignment — the part people skip
This is where most "but SPF passes!" tickets end. DMARC does not care that SPF passed; it cares that the domain SPF passed for matches the domain in the From: header. An ESP that bounces to bounce.esp-vendor.example gives you a genuine SPF pass with zero alignment, and if DKIM is also unsigned or signed with the vendor's own domain, DMARC fails outright.
| Check | What it compares | Passes when |
|---|---|---|
| SPF authentication | Connecting IP vs the SPF record of the MAIL FROM domain | The IP is authorized |
| SPF alignment | MAIL FROM domain vs From: header domain | They match (relaxed: same organizational domain) |
| DKIM authentication | Signature vs the published key at s=._domainkey.d= | The signature verifies |
| DKIM alignment | d= domain vs From: header domain | They match (relaxed: same organizational domain) |
| DMARC overall | The four results above | Either SPF or DKIM both authenticates and aligns |
Fix alignment by configuring a custom Return-Path subdomain at the vendor (which fixes SPF alignment) or by having the vendor sign with a DKIM key on your domain (which fixes DKIM alignment). Either one is sufficient. The DMARC checker shows your published policy and the alignment modes it requests.
4. PTR — does the sending IP have correct reverse DNS?
Microsoft and Yahoo both treat a missing or generic PTR as a strong negative signal, and some receivers refuse outright. The requirement is forward-confirmed reverse DNS: the IP resolves to a hostname, and that hostname resolves back to the same IP.
# Reverse: IP -> hostname
dig +short -x 203.0.113.25
# mail.example.com.
# Forward: hostname -> IP (must come back to the same address)
dig +short A mail.example.com
# One-liner sanity check on a known-good address
dig +short -x 8.8.8.8
# dns.google.A PTR that looks like 203-0-113-25.dynamic.isp.example reads as a residential or dynamic address and will be treated accordingly. PTR is set by whoever owns the IP block — your hosting provider or VPS panel, never your own DNS zone. See set a PTR record on your VPS and check yours with the reverse DNS lookup.
5. Reputation and listings
Only now is it worth looking at reputation. Check the sending IP against public blocklists with the blacklist check, and confirm the MTA is reachable and offering TLS on the submission ports with the SMTP test. If you send at volume to Microsoft, enrol in their postmaster and sender-support programmes; if you send to Gmail, Postmaster Tools shows your domain and IP reputation and your reported spam rate.
- A shared IP carries other tenants' reputation. If you are on shared hosting and cannot fix reputation, move to a dedicated sending IP or a reputable ESP.
- A new IP with no history gets throttled and refused more readily. Warm it up gradually rather than sending a full campaign on day one.
- A compromised account or web form sending spam through your relay will get you blocked faster than anything else. Check your outbound queue for volume you did not generate.
- Listing removal is pointless while the cause is still sending. Fix the source, then request delisting.
What clears the rejection?
In roughly descending order of how often it is the answer: publish or repair SPF so it evaluates without permerror; enable DKIM signing and publish the key; align at least one of the two with the From: domain; fix or set a meaningful PTR; and stop whatever is generating complaints. Once the records are right, the rejection usually stops on the next attempt — receivers evaluate authentication per message, not from a cached verdict. Reputation-driven blocks take longer, because they are based on a rolling history that has to age out.