---
title: "Validating Email Authenticity"
canonical: "https://test.abhinandan.one/go-validating-email-authenticity"
markdown_url: "https://test.abhinandan.one/go-validating-email-authenticity.md"
publisher: "Primitive SDKs"
kind: "guide"
content_type: "reference"
category: "Go SDK"
description: "ValidateEmailAuth computes a legit/suspicious/unknown verdict from SPF/DKIM/DMARC, and IsTrustedSender anchors that verdict to an expected From domain."
keywords: ["ValidateEmailAuth", "IsTrustedSender", "TrustedSenderOptions", "domain-anchored sender trust", "email authenticity verdict", "AuthVerdictLegit"]
last_modified: "2026-08-11T18:54:55.794812+00:00"
published_at: "2026-08-11T18:54:55.626901+00:00"
source_files:
  - "sdk-go/webhook.go"
sections:
  - {anchor: "compute-the-raw-authenticity-verdict", title: "Compute the raw authenticity verdict"}
  - {anchor: "anchor-the-verdict-to-a-domain-with-istrustedsender", title: "Anchor the verdict to a domain with IsTrustedSender"}
  - {anchor: "step-get-the-raw-emailreceived-event", title: "Get the raw email.received event"}
  - {anchor: "step-call-istrustedsender-with-the-expected-domain", title: "Call IsTrustedSender with the expected domain"}
  - {anchor: "step-branch-on-trusted-retryable-and-reason", title: "Branch on Trusted, Retryable, and Reason"}
  - {anchor: "expected-result", title: "Expected result"}
  - {anchor: "next-steps", title: "Next steps"}
---

> Documentation index: https://test.abhinandan.one/llms.txt

# 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](https://test.abhinandan.one/email-model.md) 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.

```go
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`:

| Field | Type | Meaning |
|---|---|---|
| `Verdict` | `AuthVerdict` | `AuthVerdictLegit`, `AuthVerdictSuspicious`, or `AuthVerdictUnknown` |
| `Confidence` | `AuthConfidence` | `AuthConfidenceHigh`, `AuthConfidenceMedium`, or `AuthConfidenceLow` |
| `Reasons` | `[]string` | Human-readable explanation, ordered most-significant first |

The verdict logic, in order:

- **DMARC `temperror`/`permerror`** → `unknown`, low confidence: a DNS or policy lookup failure means authenticity can't be determined.
- **DMARC `pass`** → `legit`. 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 `fail`** → `suspicious`. 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. Get the raw email.received event

`IsTrustedSender` takes the raw `email.received` payload (`event.Raw` on a [`ReceivedEmail`](https://test.abhinandan.one/email-model.md), or the `EmailReceivedEvent` you get from `primitive.HandleWebhook`), not the normalized `ReceivedEmail` object.

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

### 2. Call IsTrustedSender with the expected domain

```go
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:

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

### 3. Branch on Trusted, Retryable, and Reason

```go
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:

```text
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.
