{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/go-validating-email-authenticity","markdown_url":"https://test.abhinandan.one/go-validating-email-authenticity.md","article":{"id":"eb487c22-5464-4ec0-ad8f-948662924ef1","article_slug":"go-validating-email-authenticity","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:54:55.626901+00:00","keywords":["ValidateEmailAuth","IsTrustedSender","TrustedSenderOptions","domain-anchored sender trust","email authenticity verdict","AuthVerdictLegit"],"meta_description":"ValidateEmailAuth computes a legit/suspicious/unknown verdict from SPF/DKIM/DMARC, and IsTrustedSender anchors that verdict to an expected From domain.","og_image_url":null,"source_file_paths":["sdk-go/webhook.go"],"recording_id":null,"replayable":false,"task_name":"Validating Email Authenticity","category":"Go SDK","summary":null,"description":"Compute an email authenticity verdict from SPF, DKIM, and DMARC results with ValidateEmailAuth, then anchor authorization decisions to an expected From domain with IsTrustedSender.","content_kind":"repo_page","content_markdown":"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.\n\n<Warning>\n\n`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.\n\n</Warning>\n\n## Compute the raw authenticity verdict\n\nEvery [`email.received` event](email-model) 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.\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\tprimitive \"github.com/primitivedotdev/sdks/sdk-go\"\n)\n\nfunc handleEvent(event primitive.EmailReceivedEvent) {\n\tresult, err := primitive.ValidateEmailAuth(event.Email.Auth)\n\tif err != nil {\n\t\tlog.Printf(\"invalid auth input: %v\", err)\n\t\treturn\n\t}\n\n\tswitch result.Verdict {\n\tcase primitive.AuthVerdictLegit:\n\t\tlog.Printf(\"legit (%s confidence): %v\", result.Confidence, result.Reasons)\n\tcase primitive.AuthVerdictSuspicious:\n\t\tlog.Printf(\"suspicious (%s confidence): %v\", result.Confidence, result.Reasons)\n\tdefault:\n\t\tlog.Printf(\"unknown (%s confidence): %v\", result.Confidence, result.Reasons)\n\t}\n}\n```\n\n`ValidateEmailAuth` accepts any value that decodes into the `EmailAuth` shape and returns a `*WebhookValidationError` if it doesn't. On success you get a `ValidateEmailAuthResult`:\n\n| Field | Type | Meaning |\n|---|---|---|\n| `Verdict` | `AuthVerdict` | `AuthVerdictLegit`, `AuthVerdictSuspicious`, or `AuthVerdictUnknown` |\n| `Confidence` | `AuthConfidence` | `AuthConfidenceHigh`, `AuthConfidenceMedium`, or `AuthConfidenceLow` |\n| `Reasons` | `[]string` | Human-readable explanation, ordered most-significant first |\n\nThe verdict logic, in order:\n\n- **DMARC `temperror`/`permerror`** → `unknown`, low confidence: a DNS or policy lookup failure means authenticity can't be determined.\n- **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).\n- **DMARC `fail`** → `suspicious`. Confidence is `high` under a `reject` or `quarantine` policy, `medium` when SPF also failed under `none` policy, `low` otherwise.\n- **DMARC `none`** (no record published) → `unknown` unless SPF hard-fails, in which case it's `suspicious`, `medium` confidence.\n\n## Anchor the verdict to a domain with IsTrustedSender\n\n`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*.\n\n<Steps>\n\n<Step title=\"Get the raw email.received event\">\n\n`IsTrustedSender` takes the raw `email.received` payload (`event.Raw` on a [`ReceivedEmail`](email-model), or the `EmailReceivedEvent` you get from `primitive.HandleWebhook`), not the normalized `ReceivedEmail` object.\n\n```go\nemail, err := primitive.Receive(primitive.HandleWebhookOptions{\n\tBody:    body,\n\tHeaders: headers,\n\tSecret:  \"whsec_...\",\n})\nif err != nil {\n\tlog.Printf(\"invalid webhook: %v\", err)\n\treturn\n}\n```\n\n</Step>\n\n<Step title=\"Call IsTrustedSender with the expected domain\">\n\n```go\ntrust, err := primitive.IsTrustedSender(email.Raw, primitive.TrustedSenderOptions{\n\tDomain: \"example.com\",\n})\nif err != nil {\n\t// invalid TrustedSenderOptions (e.g. empty Domain)\n\tlog.Printf(\"bad trust options: %v\", err)\n\treturn\n}\n```\n\nPass `Sender` too when the email must come from one exact address, not just the domain:\n\n```go\ntrust, err := primitive.IsTrustedSender(email.Raw, primitive.TrustedSenderOptions{\n\tDomain: \"example.com\",\n\tSender: \"billing@example.com\",\n})\n```\n\n</Step>\n\n<Step title=\"Branch on Trusted, Retryable, and Reason\">\n\n```go\nswitch {\ncase trust.Trusted:\n\t// Authenticated mail whose From address is @example.com\n\t// (and matches Sender exactly, when given).\n\tapprovePayment()\ncase trust.Retryable:\n\t// Transient DNS failure during DMARC evaluation. Respond with a\n\t// 5xx so webhook redelivery retries this email later.\n\trespondServerError()\ndefault:\n\tlog.Printf(\"untrusted: %s %v\", trust.Reason, trust.Auth.Reasons)\n\trejectSilently()\n}\n```\n\n`Trusted` is `true` only when **all** of the following hold:\n\n- The underlying verdict is `legit`.\n- The domain DMARC evaluated equals `Domain`.\n- The From header strict-parses to a single valid address in `Domain` (exactly matching `Sender`, when given).\n\n`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).\n\n</Step>\n\n</Steps>\n\n## Expected result\n\nA trusted, DMARC-aligned email from `example.com` produces:\n\n```text\nlegit (high confidence): [DMARC passed with DKIM alignment (example.com)]\n```\n\nand `IsTrustedSender` with `Domain: \"example.com\"` returns `trust.Trusted == true`.\n\nAn 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.\n\n<Tip>\n\nDo 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.\n\n</Tip>\n\n<Warning>\n\nNever 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.\n\n</Warning>\n\nThe 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.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Inbound and Outbound Email Model\" href=\"email-model\">\n\nLearn the normalized ReceivedEmail object and where email.Raw / event.Auth come from.\n\n</Card>\n\n<Card title=\"Receiving and Verifying Webhooks\" href=\"go-receiving-webhooks\">\n\nSee how Receive and ReceiveFromHTTPRequest verify signatures and normalize inbound mail before you validate auth.\n\n</Card>\n\n<Card title=\"Webhook Event Types\" href=\"go-webhook-event-types\">\n\nUnderstand the email, payment, and interaction event families this auth check applies within.\n\n</Card>\n\n<Card title=\"Error Handling\" href=\"go-error-handling\">\n\nLook up WebhookValidationError and the other error types ValidateEmailAuth and IsTrustedSender can return.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-go/webhook.go","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Validating+Email+Authenticity&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fgo-validating-email-authenticity","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}