DEV Community

FrostY45
FrostY45

Posted on

E-commerce Mail Spam After Cutover: 4 Checks for SPF, DKIM, and DMARC

A deliverability page usually arrives after the damage: a customer says an order receipt landed in spam, while the dashboard reports that SPF, DKIM, and DMARC are all configured. The useful question is narrower. Does the domain in the visible From: header align with the domain that actually passed SPF or DKIM?

TL;DR: DMARC passes when either SPF or DKIM both passes authentication and aligns with the visible From domain. Start with the receiver's Authentication-Results header, then verify the exact TXT records and the MX target used during the cutover. Treat DNS propagation as a bounded observation window, not as proof that a record is correct.

In an e-commerce cutover, that distinction matters. The storefront may switch transactional mail in one deploy, but resolvers and receiving systems learn DNS at different times. A fast change with a fuzzy rollback plan creates the same kind of page as a missed queue job: the alert is real, but its evidence is incomplete.

The page that fires after the customer complaint

The on-call view often shows a rise in spam placement, a handful of DMARC failures, and a recent DNS change. The tempting action is to edit every record. Resist that. Capture one failed message and one message that arrived normally. Save the complete headers, the sending timestamp, the envelope sender, and the receiver's verdict.

Authentication-Results is the first useful breadcrumb. It can show spf=pass, dkim=pass, and dmarc=fail in the same message. That combination is not contradictory: authentication can pass for a domain that does not align with the visible From domain. A forwarding path can also alter SPF while leaving a valid DKIM signature, so the two mechanisms need separate inspection.

The signal that should have fired earlier is a change in aligned DMARC results, split by sending stream. An aggregate report that says 99 percent of messages passed overall can hide a new receipt service sending from a different subdomain. Alert on the stream and the aligned result, not only on total volume.

Headers first.

Stop there.

Why Is Mail Going to Spam After SPF and DKIM Setup?

Use the records to answer four different questions; none of them is “did we publish a TXT value?”

Check Evidence to collect Typical failure it exposes
Visible identity The RFC 5322 From: domain A branded address differs from the authenticated domain
SPF path Return-Path (envelope-from), the SPF result, and the evaluated TXT policy The sender is authorized, but the envelope domain is unrelated to From
DKIM path d= in the signature, selector, and dkim= result The signature passes for a signing domain that does not align
DMARC decision dmarc= result and policy applied Neither authenticated path both passes and aligns

DMARC alignment is defined against the organizational domain in relaxed mode, or an exact domain match in strict mode. The policy's adkim and aspf tags choose that mode. A message from orders.example.com can therefore align with example.com in relaxed mode, while a strict policy requires the same domain string. Do not infer the mode from a DNS host name; read the published DMARC policy.

Here is a small diagnostic model I use in a runbook. It intentionally checks exact domains only; a production implementation must apply the public suffix rules needed to calculate organizational domains.

type AuthPath struct {
    Pass   bool
    Domain string
}

func dmarcAligned(from string, spf, dkim AuthPath, strict bool) bool {
    if !spf.Pass && !dkim.Pass {
        return false
    }
    match := func(domain string) bool {
        if strict {
            return domain == from
        }
        // Replace with a public-suffix-list lookup for relaxed alignment.
        return domain == from
    }
    return (spf.Pass && match(spf.Domain)) || (dkim.Pass && match(dkim.Domain))
}
Enter fullscreen mode Exit fullscreen mode

The code is a guardrail against a common review mistake: treating a green SPF result as a green DMARC result. It is not a DNS parser, and it should never replace the receiver's headers or aggregate reports.

Work backward to the earlier signal

After identifying the failing path, inspect DNS from the perspective of several resolvers. Query the authoritative nameserver and at least two recursive resolvers, recording the answer and its TTL. During a cutover, a resolver can legitimately return the old MX or TXT answer until its cached TTL expires. That is propagation delay, not an alignment verdict.

For the e-commerce job, I define a cutover window before changing the visible From domain. During that window, the new sender publishes its SPF authorization, DKIM selector, and DMARC policy while the old sender remains valid. A staged message uses the final From domain and is sampled at each receiving environment. Only after aligned passes are stable do we change routing. The exact duration depends on the published TTL and the receivers involved; the decision is based on observed answers, not a universal “DNS takes X hours” promise.

On rotations where missed jobs and duplicate deliveries already compete for attention, I use a 15-minute sample window and two resolver vantage points before declaring a change healthy. It is a deliberately small experiment: enough messages to expose a wrong selector or envelope domain, short enough to stop a bad rollout before the queue fills.

The sample includes one message generated before the DNS edit, one after the signer switch, and one after each resolver reports the new TXT answer. I record the selector and the d= value beside the receiver verdict, because a passing signature with yesterday's selector is evidence of cache overlap, not evidence that the new key is serving traffic. If those observations disagree, I leave routing on the old sender and extend the window rather than guessing.

MX records deserve their own check. MX controls where inbound mail for the domain is delivered; it does not authenticate outbound receipts. Pointing MX at the right inbound service while leaving an old SPF include or an unprovisioned DKIM selector can produce a cutover that looks complete in the control panel and fails in headers. Keep the inbound MX change and outbound authentication change as separate rollout steps with separate rollback switches.

One operational trap is selector reuse. If a selector's TXT value is replaced while messages signed with the previous key are still in flight, receivers may report intermittent DKIM failures. Publish a new selector, switch signers, wait through the message lifetime you support, then retire the old selector. The extra DNS record is cheaper than guessing which key a receiver cached.

How do we set an alert without paging on noise?

The instrumentation change is straightforward: emit one event per sampled message containing the From domain, envelope-from domain, DKIM d=, selector, SPF result, DKIM result, DMARC result, policy mode, and resolver answer versions. Hash addresses or remove local parts so the event cannot become a mailbox store. Keep the raw headers in a restricted incident record with a retention limit.

A useful alert has two conditions: aligned DMARC pass falls below the agreed service objective for a sending stream, and the change correlates with a DNS or signer deployment. A single failed message should open an investigation, not wake the whole rotation. Conversely, a broad threshold can hide a small but important stream, such as password resets.

The false-positive cost is real. Page on every transient failure and the team starts bypassing the alert; page too late and customers miss receipts. I prefer a warning on the first report interval, a page after a second interval with the same failing domain, and a dashboard that shows raw counts beside percentages. Percentages alone are misleading when a stream sends 20 messages.

When a page fires, the action order is stable:

  1. Freeze further identity or DNS changes.
  2. Compare a passing and failing header, including the exact domains.
  3. Confirm authoritative and recursive DNS answers plus TTLs.
  4. Roll back the sender or policy only if the evidence points to the deployment, and leave an incident note explaining the alignment path that failed.

That last note prevents a second outage. The next engineer should not have to rediscover that SPF passed for the envelope domain while DKIM was the only aligned mechanism.

A cutover decision you can defend

Choose speed when the old and new senders can coexist, the final From domain is already covered by a passing DKIM signature, and resolver observations agree. Choose a slower staged change when the sender requires a new selector, the DMARC policy is strict, or inbound MX and outbound identity are changing together.

The decision is about evidence and reversibility. A short TTL may make a rollback faster for new lookups, but it cannot evict cached answers immediately. A long TTL reduces query churn, but it extends the period in which receivers can see different policies. Document the trade-off in the change ticket, attach sample headers, and set an expiry for temporary SPF authorization.

Spam placement has other causes, including reputation and content, so alignment is a necessary diagnostic boundary rather than a guarantee of inbox delivery. Once aligned DMARC passes are stable, move to those other signals. Do not keep rewriting DNS records to solve a problem the headers no longer show.

Further reading

Top comments (0)