DEV Community

CalderHayes9638
CalderHayes9638

Posted on

Mail Going to Spam After SPF DKIM DMARC Setup with Node.js Alignment Debugging

Short answer: inspect the delivered message's From, SPF, and DKIM domains; the aligned SPF or DKIM identity, not a green DNS console, explains why mail is going to spam.

The decisive trade-off is between declaring authentication success and proving that the visible From domain is aligned with the authenticated identity. In a marketplace where each seller can point a custom domain at the product, SPF, DKIM, and DMARC can all return green while messages still land in spam. The practical answer is to debug the receiver's evidence chain: capture the SMTP result, compare RFC 5322.From with the SPF and DKIM domains, then correlate the decision with reputation and content signals.

A useful mental model is a ledger. Each outbound message gets an immutable event containing the envelope sender, header From, DKIM d= value, selector, sending IP, and the receiver's Authentication-Results. Without that audit trail, a DNS lookup is only a hypothesis. I initially treated a successful SPF lookup as the answer; tracing a forwarded message showed why that shortcut fails.

Check the final hop.

There is no shortcut.

Which identity is the receiver actually aligning?

DMARC alignment is a relationship, not a single record. The domain in the visible From header must align with either the domain authenticated by SPF or the domain in the DKIM signature. RFC 7489 defines relaxed alignment as organizational-domain matching and strict alignment as an exact match. A marketplace should make this explicit per tenant, because seller.example and mailer.example may pass relaxed alignment while a separate forwarding path changes the result.

The first debugging question is therefore narrow: did the message that reached the mailbox carry the domain you configured? Inspect the raw message, not a dashboard summary. A common failure is generating a DKIM signature for the platform's shared domain while templating a seller's From address. DKIM can be cryptographically valid in that case, yet DMARC has no aligned identifier if SPF also uses another domain.

What evidence should a Node.js service retain?

I treat delivery evidence as a reconciliation record. The send request is one fact; the receiver's verdict is another. They must be joinable by a stable message identifier, and retries must not create ambiguous rows. This small Go example shows the shape of a record that can be stored in Postgres or an append-only log without coupling the design to a provider API.

type DeliveryEvidence struct {
    MessageID       string
    EnvelopeFrom    string
    HeaderFrom      string
    SPFDomain       string
    DKIMDomain      string
    DKIMResult      string
    DMARCResult     string
    ReceivingDomain string
    ObservedAt      time.Time
}

func aligned(from, authenticated string, relaxed bool) bool {
    if !relaxed {
        return strings.EqualFold(from, authenticated)
    }
    return organizationalDomain(from) == organizationalDomain(authenticated)
}
Enter fullscreen mode Exit fullscreen mode

The code deliberately omits a shortcut that marks a message delivered after the enqueue call. Exactly-once behavior is an accounting goal, not a promise made by SMTP; persist an idempotency key, accept duplicate callbacks safely, and preserve the original evidence when a later event disagrees.

Why is mail going to spam after SPF and DKIM pass?

Authentication answers “who authorized this message?” It does not answer “should this mailbox accept it?” Receivers also evaluate complaint history, sudden volume changes, malformed headers, list-unsubscribe behavior, and the consistency of the sending infrastructure. For custom domains, a new seller can inherit none of the platform's history, so a passing DMARC result may coexist with cautious filtering. That is an operational constraint, not proof that DNS is broken.

Forwarding is another sharp edge. SPF often fails after an intermediary changes the connecting IP, while DKIM may survive if the signed headers remain intact. ARC can preserve authenticated results across a trusted forwarding chain, but it does not turn an unrelated sender into an aligned one. Record the Authentication-Results header from the final receiving hop and inspect which identifier supplied the pass.

I also separate policy rollout from diagnosis. Start with DMARC reporting and a narrow sample, verify alignment on real messages, then move toward quarantine or reject according to the organization's abuse and compliance limits. Reports contain aggregate signals and can include sensitive metadata; retain only what the privacy and regulatory review permits, and define an expiry for raw samples.

For each seller domain, verify DNS ownership, publish the DKIM selector, and require a test message whose From address exactly matches the intended brand domain. Store the raw headers for a bounded period, hash message bodies when content inspection is unnecessary, and expose a per-tenant timeline: configuration change, send attempt, SMTP response, and receiver report. That timeline lets an operator distinguish propagation delay from an alignment defect without guessing.

The migration gate is evidence-based: no tenant moves to an enforcing DMARC policy until a representative set of messages shows either SPF or DKIM passing with alignment at the final receiver, including a forwarded case where the business depends on forwarding. Keep a rollback path for policy mode, but never delete the audit record that explains why it was used.

The concise rule is this: debug the domain in the delivered message, not the domain in your DNS console. SPF and DKIM are inputs; DMARC alignment and receiver evidence are the decision boundary.

Sources

Top comments (0)