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

Authenticating Senders with SPF, DKIM, and DMARC

Compute an email authenticity verdict from an inbound email's SPF/DKIM/DMARC results, then anchor it to an expected From domain with is_trusted_sender before gating any action on "this really came from our domain."

is_trusted_sender decides whether an inbound email really came from a domain you trust, not just whether it authenticated at all. Reach for it before you let an inbound message trigger a refund, a payout, a config change, or any other action a spoofed sender shouldn't be able to trigger.

Every email.received event carries the server's SPF, DKIM, and DMARC results on event.email.auth. Two functions turn those results into a decision:

  • validate_email_auth(event.email.auth): computes an email authenticity verdict, legit, suspicious, or unknown, with a confidence level and human-readable reasons.
  • is_trusted_sender(email.raw, domain=...): performs domain-anchored sender trust, it anchors that verdict to an expected From domain, which is the check you actually want for authorization decisions.

Why the verdict alone isn't enough#

A legit verdict proves the email authenticated as its own From domain, not as a domain you trust, so gating on the verdict alone lets any attacker-owned authenticated domain through. validate_email_auth answers "was this email authenticated?", not "authenticated as which domain?" A fully authenticated email from any domain, including one an attacker registered five minutes ago, returns legit. If you gate a privileged action on verdict == "legit" alone, an attacker who owns their own domain and configures SPF/DKIM/DMARC correctly for it sails right through.

is_trusted_sender closes that gap by checking that the domain DMARC evaluated matches the domain you expected, and that the From header parses to a single valid address in that domain.

Warning

Never authorize based on email.reply_target or email.smtp.mail_from. Both are fully sender-controlled. The normalized email.sender is not a safe anchor either: it is parsed leniently for display and falls back to the SMTP envelope sender.

Compute the bare verdict#

Call validate_email_auth(event.email.auth) to turn the server's SPF, DKIM, and DMARC results into a verdict, a confidence level, and a list of reasons.

  1. 1

    Call validate_email_auth with the event's auth object#

    from primitive import validate_email_auth
    
    result = validate_email_auth(event.email.auth)
    
    print(result.verdict)      # "legit" | "suspicious" | "unknown"
    print(result.confidence)   # "high" | "medium" | "low"
    print(result.reasons)      # e.g. ["DMARC passed with DKIM alignment (example.com)"]
    
  2. 2

    Read the verdict, but don't gate on it alone#

    A legit verdict only tells you the email authenticated as some domain. Treat it as an input to a further decision, not the decision itself, proceed to is_trusted_sender for anything that grants access or triggers an action.

Anchor the verdict to a domain you trust#

Call is_trusted_sender(email.raw, domain="example.com") to check the verdict against an expected From domain. This is the check to use for real authorization decisions: "did this email really come from example.com?"

  1. 1

    Import is_trusted_sender#

    from primitive import is_trusted_sender
    
  2. 2

    Call it with the raw event and your expected domain#

    trust = is_trusted_sender(email.raw, domain="example.com")
    

    email.raw is the EmailReceivedEvent attached to the normalized ReceivedEmail object, the same shape validate_email_auth reads from. Pass sender="ceo@example.com" too if you need to anchor to one exact address, not just the domain.

  3. 3

    Branch on trusted, retryable, and the fallback case#

    if trust.trusted:
        ...  # authenticated mail whose From address is @example.com
    elif trust.retryable:
        # Transient DNS failure during DMARC evaluation. Respond with a 5xx
        # so webhook redelivery retries this email later.
        ...
    else:
        print("untrusted:", trust.reason, trust.auth.reasons)
    

Expected result#

  • trust.trusted is True only when all of the following hold:
    • the verdict is legit
    • the domain DMARC evaluated equals the domain you passed
    • the From header strict-parses to a single valid address in that domain (exactly matching sender= when you passed one)
  • trust.reason is a stable, machine-readable code naming the first check that failed, so you can branch on it without inspecting message text.
  • trust.auth.reasons carries the underlying validate_email_auth reasons for debugging.
Tip

An unknown verdict has two very different causes bundled together: a temporary DNS error (worth retrying) and a sender domain that publishes no DMARC record at all (permanent for that email). is_trusted_sender separates them for you via trust.retryable, check it before deciding whether to retry or reject outright.

What NOT to do#

Don't hand-roll the check: three mistakes make a homegrown sender check spoofable.

  • 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. Only a strict RFC 5322 parse catches this.
  • Trusting email.reply_target or email.smtp.mail_from. Both are sender-controlled and carry no authentication guarantee.
  • Trusting the normalized email.sender. It's parsed leniently for display and falls back to the SMTP envelope sender when the From header doesn't parse cleanly, so it is not a safe authorization anchor.
Note

The same helper exists with identical semantics in the other SDKs: isTrustedSender in the Node.js SDK and IsTrustedSender in the Go SDK. Pick the one for your language; the trust model is identical.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer