Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /go-validating-email-authenticity.md.
Verified · 8/11/2026

Validating Email Authenticity

Compute an email authenticity verdict from SPF, DKIM, and DMARC results with ValidateEmailAuth, then anchor authorization decisions to an expected From domain with IsTrustedSender.

Use primitive.ValidateEmailAuth when you need to know whether an inbound email's SPF, DKIM, and DMARC results add up to a trustworthy authentication outcome. Use primitive.IsTrustedSender when the decision is really "did this really come from example.com", the check most handlers actually need before taking an authorized action like approving a payment or granting access.

Warning

ValidateEmailAuth alone does not tell you which domain authenticated. A fully authenticated email from any domain, including one an attacker registered, returns legit. Never gate an authorization decision on the bare verdict; anchor it with IsTrustedSender instead.

Compute the raw authenticity verdict#

Every email.received event carries the server's SPF, DKIM, and DMARC results on event.Email.Auth. ValidateEmailAuth turns those into an overall email authenticity verdict: legit, suspicious, or unknown, with a confidence level and human-readable reasons.

package main

import (
	"log"

	primitive "github.com/primitivedotdev/sdks/sdk-go"
)

func handleEvent(event primitive.EmailReceivedEvent) {
	result, err := primitive.ValidateEmailAuth(event.Email.Auth)
	if err != nil {
		log.Printf("invalid auth input: %v", err)
		return
	}

	switch result.Verdict {
	case primitive.AuthVerdictLegit:
		log.Printf("legit (%s confidence): %v", result.Confidence, result.Reasons)
	case primitive.AuthVerdictSuspicious:
		log.Printf("suspicious (%s confidence): %v", result.Confidence, result.Reasons)
	default:
		log.Printf("unknown (%s confidence): %v", result.Confidence, result.Reasons)
	}
}

ValidateEmailAuth accepts any value that decodes into the EmailAuth shape and returns a *WebhookValidationError if it doesn't. On success you get a ValidateEmailAuthResult:

FieldTypeMeaning
VerdictAuthVerdictAuthVerdictLegit, AuthVerdictSuspicious, or AuthVerdictUnknown
ConfidenceAuthConfidenceAuthConfidenceHigh, AuthConfidenceMedium, or AuthConfidenceLow
Reasons[]stringHuman-readable explanation, ordered most-significant first

The verdict logic, in order:

  • DMARC temperror/permerrorunknown, low confidence: a DNS or policy lookup failure means authenticity can't be determined.
  • DMARC passlegit. Confidence is high when DKIM aligned (and no weak DKIM key was used), medium when only SPF aligned or when DKIM used a weak key (<1024 bits).
  • DMARC failsuspicious. Confidence is high under a reject or quarantine policy, medium when SPF also failed under none policy, low otherwise.
  • DMARC none (no record published) → unknown unless SPF hard-fails, in which case it's suspicious, medium confidence.

Anchor the verdict to a domain with IsTrustedSender#

IsTrustedSender is the domain-anchored sender trust check: it answers "did this authenticate as example.com," not just "did this authenticate." Use it whenever a handler is about to do something consequential, approve a payment, grant access, auto-reply with sensitive data, based on who the email is from.

  1. 1

    Get the raw email.received event#

    IsTrustedSender takes the raw email.received payload (event.Raw on a ReceivedEmail, or the EmailReceivedEvent you get from primitive.HandleWebhook), not the normalized ReceivedEmail object.

    email, err := primitive.Receive(primitive.HandleWebhookOptions{
    	Body:    body,
    	Headers: headers,
    	Secret:  "whsec_...",
    })
    if err != nil {
    	log.Printf("invalid webhook: %v", err)
    	return
    }
    
  2. 2

    Call IsTrustedSender with the expected domain#

    trust, err := primitive.IsTrustedSender(email.Raw, primitive.TrustedSenderOptions{
    	Domain: "example.com",
    })
    if err != nil {
    	// invalid TrustedSenderOptions (e.g. empty Domain)
    	log.Printf("bad trust options: %v", err)
    	return
    }
    

    Pass Sender too when the email must come from one exact address, not just the domain:

    trust, err := primitive.IsTrustedSender(email.Raw, primitive.TrustedSenderOptions{
    	Domain: "example.com",
    	Sender: "billing@example.com",
    })
    
  3. 3

    Branch on Trusted, Retryable, and Reason#

    switch {
    case trust.Trusted:
    	// Authenticated mail whose From address is @example.com
    	// (and matches Sender exactly, when given).
    	approvePayment()
    case trust.Retryable:
    	// Transient DNS failure during DMARC evaluation. Respond with a
    	// 5xx so webhook redelivery retries this email later.
    	respondServerError()
    default:
    	log.Printf("untrusted: %s %v", trust.Reason, trust.Auth.Reasons)
    	rejectSilently()
    }
    

    Trusted is true only when all of the following hold:

    • The underlying verdict is legit.
    • The domain DMARC evaluated equals Domain.
    • The From header strict-parses to a single valid address in Domain (exactly matching Sender, when given).

    Reason names the first check that failed, and trust.Auth.Reasons carries the underlying verdict's human-readable explanation. Retryable separates two very different causes: a temporary DNS error during DMARC evaluation (worth retrying later) from a sender domain that publishes no DMARC record (permanent for that email).

Expected result#

A trusted, DMARC-aligned email from example.com produces:

legit (high confidence): [DMARC passed with DKIM alignment (example.com)]

and IsTrustedSender with Domain: "example.com" returns trust.Trusted == true.

An email from an unrelated domain, even if fully authenticated as its own domain, produces AuthVerdictLegit from ValidateEmailAuth but trust.Trusted == false from IsTrustedSender, proof that the two checks answer different questions.

Tip

Do not build your own version of this check by regexing the raw From header. From: "trusted@example.com" <x@evil.com> puts an allowlisted address in the display name while DMARC evaluates, and can pass, for evil.com. Use IsTrustedSender, which strict-parses the header instead.

Warning

Never authorize based on email.ReplyTarget or email.Raw.Email.SMTP.MailFrom, both are fully sender-controlled. The normalized email.Sender on a ReceivedEmail is parsed leniently for display and falls back to the SMTP envelope sender, so it is not a safe authorization anchor either. IsTrustedSender is the one call built to be safe here.

The same two-check model (a bare verdict plus a domain-anchored trust check) is identical across SDKs: validateEmailAuth / isTrustedSender in Node, validate_email_auth / is_trusted_sender in Python.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer