{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/python-sender-trust","markdown_url":"https://test.abhinandan.one/python-sender-trust.md","article":{"id":"614c24bd-5e3b-4e8e-9cd3-b4aa4bfb8aff","article_slug":"python-sender-trust","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:55:04.356814+00:00","keywords":["is_trusted_sender","validate_email_auth","domain-anchored sender trust","email authenticity verdict","retryable DMARC","primitive python sdk auth"],"meta_description":"is_trusted_sender anchors a validate_email_auth verdict to an expected From domain, letting Python SDK handlers gate actions only on authenticated senders.","og_image_url":null,"source_file_paths":["sdk-python/README.md","sdk-python/src/primitive/webhook.py"],"recording_id":null,"replayable":false,"task_name":"Authenticating Senders with SPF, DKIM, and DMARC","category":"Python SDK","summary":null,"description":"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.\"","content_kind":"repo_page","content_markdown":"`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.\n\nEvery [`email.received` event](email-model) carries the server's SPF, DKIM, and DMARC results on `event.email.auth`. Two functions turn those results into a decision:\n\n- **`validate_email_auth(event.email.auth)`**: computes an **email authenticity verdict**, `legit`, `suspicious`, or `unknown`, with a confidence level and human-readable reasons.\n- **`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.\n\n## Why the verdict alone isn't enough\n\nA `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.\n\n`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.\n\n<Warning>\n\nNever 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.\n\n</Warning>\n\n## Compute the bare verdict\n\nCall `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.\n\n<Steps>\n\n<Step title=\"Call validate_email_auth with the event's auth object\">\n\n```python\nfrom primitive import validate_email_auth\n\nresult = validate_email_auth(event.email.auth)\n\nprint(result.verdict)      # \"legit\" | \"suspicious\" | \"unknown\"\nprint(result.confidence)   # \"high\" | \"medium\" | \"low\"\nprint(result.reasons)      # e.g. [\"DMARC passed with DKIM alignment (example.com)\"]\n```\n\n</Step>\n\n<Step title=\"Read the verdict, but don't gate on it alone\">\n\nA `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.\n\n</Step>\n\n</Steps>\n\n## Anchor the verdict to a domain you trust\n\nCall `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`?\"\n\n<Steps>\n\n<Step title=\"Import is_trusted_sender\">\n\n```python\nfrom primitive import is_trusted_sender\n```\n\n</Step>\n\n<Step title=\"Call it with the raw event and your expected domain\">\n\n```python\ntrust = is_trusted_sender(email.raw, domain=\"example.com\")\n```\n\n`email.raw` is the [`EmailReceivedEvent`](email-model) attached to the normalized [`ReceivedEmail`](email-model) 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.\n\n</Step>\n\n<Step title=\"Branch on trusted, retryable, and the fallback case\">\n\n```python\nif trust.trusted:\n    ...  # authenticated mail whose From address is @example.com\nelif trust.retryable:\n    # Transient DNS failure during DMARC evaluation. Respond with a 5xx\n    # so webhook redelivery retries this email later.\n    ...\nelse:\n    print(\"untrusted:\", trust.reason, trust.auth.reasons)\n```\n\n</Step>\n\n</Steps>\n\n### Expected result\n\n- `trust.trusted` is `True` only when **all** of the following hold:\n  - the verdict is `legit`\n  - the domain DMARC evaluated equals the `domain` you passed\n  - the From header strict-parses to a single valid address in that domain (exactly matching `sender=` when you passed one)\n- `trust.reason` is a stable, machine-readable code naming the first check that failed, so you can branch on it without inspecting message text.\n- `trust.auth.reasons` carries the underlying `validate_email_auth` reasons for debugging.\n\n<Tip>\n\nAn `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.\n\n</Tip>\n\n## What NOT to do\n\nDon't hand-roll the check: three mistakes make a homegrown sender check spoofable.\n\n- **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.\n- **Trusting `email.reply_target` or `email.smtp.mail_from`.** Both are sender-controlled and carry no authentication guarantee.\n- **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.\n\n<Note>\n\nThe same helper exists with identical semantics in the other SDKs: `isTrustedSender` in the [Node.js SDK](node-sdk-email-authenticity) and `IsTrustedSender` in the [Go SDK](go-validating-email-authenticity). Pick the one for your language; the trust model is identical.\n\n</Note>\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Receiving and Parsing Inbound Email\" href=\"python-receive-email\">\n\nSee where email.raw and the auth object come from on the normalized ReceivedEmail shape.\n\n</Card>\n\n<Card title=\"Verifying Webhook Signatures\" href=\"python-webhook-verification\">\n\nVerify the webhook delivery itself is authentic before you even get to sender trust.\n\n</Card>\n\n<Card title=\"Python SDK Error Reference\" href=\"python-errors-reference\">\n\nLook up the error raised when validate_email_auth receives a malformed auth object.\n\n</Card>\n\n<Card title=\"Inbound and Outbound Email Model\" href=\"email-model\">\n\nUnderstand the full ReceivedEmail shape and where the auth field fits.\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-python/README.md","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Authenticating+Senders+with+SPF%2C+DKIM%2C+and+DMARC&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fpython-sender-trust","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}