Authentication Results
VaultSandbox validates email authentication for every received email, providing detailed SPF, DKIM, DMARC, and reverse DNS results.
What is Email Authentication?
Section titled “What is Email Authentication?”Email authentication helps verify that an email:
- Came from the claimed sender domain (SPF)
- Wasn’t modified in transit (DKIM)
- Complies with the domain’s policy (DMARC)
- Came from a legitimate mail server (Reverse DNS)
AuthResults Object
Section titled “AuthResults Object”Every email has an auth_results property:
from vaultsandbox import WaitForEmailOptions
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
auth = email.auth_results
print(auth.spf) # SPFResult or Noneprint(auth.dkim) # list[DKIMResult]print(auth.dmarc) # DMARCResult or Noneprint(auth.reverse_dns) # ReverseDNSResult or NoneSPF (Sender Policy Framework)
Section titled “SPF (Sender Policy Framework)”Verifies the sending server is authorized to send from the sender’s domain.
SPF Result Structure
Section titled “SPF Result Structure”from vaultsandbox.types import SPFStatus
spf = email.auth_results.spf
if spf: print(spf.result) # SPFStatus.PASS, SPFStatus.FAIL, etc. print(spf.domain) # Domain checked print(spf.ip) # IP address checked print(spf.details) # Human-readable detailsSPF Status Values
Section titled “SPF Status Values”| Status | Meaning |
|---|---|
PASS | Sending server is authorized |
FAIL | Sending server is NOT authorized |
SOFTFAIL | Probably not authorized (policy says ~all) |
NEUTRAL | Domain makes no assertion |
TEMPERROR | Temporary error during check |
PERMERROR | Permanent error in SPF record |
NONE | No SPF record found |
SKIPPED | Check was skipped (see below) |
SPF Example
Section titled “SPF Example”from vaultsandbox.types import SPFStatus
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
if email.auth_results.spf: spf = email.auth_results.spf
assert spf.result == SPFStatus.PASS assert spf.domain == "example.com"
print(f"SPF {spf.result.value} for {spf.domain}")DKIM (DomainKeys Identified Mail)
Section titled “DKIM (DomainKeys Identified Mail)”Cryptographically verifies the email hasn’t been modified and came from the claimed domain.
DKIM Result Structure
Section titled “DKIM Result Structure”from vaultsandbox.types import DKIMStatus
dkim = email.auth_results.dkim # List of results
if dkim: for result in dkim: print(result.result) # DKIMStatus.PASS, DKIMStatus.FAIL, etc. print(result.domain) # Signing domain print(result.selector) # DKIM selector print(result.signature) # DKIM signature informationNote: An email can have multiple DKIM signatures (one per signing domain).
DKIM Status Values
Section titled “DKIM Status Values”| Status | Meaning |
|---|---|
PASS | Signature is valid |
FAIL | Signature is invalid |
NONE | No DKIM signature found |
SKIPPED | Check was skipped (see below) |
DKIM Example
Section titled “DKIM Example”from vaultsandbox.types import DKIMStatus
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
if email.auth_results.dkim: dkim = email.auth_results.dkim[0]
assert dkim.result == DKIMStatus.PASS assert dkim.domain == "example.com"
print(f"DKIM {dkim.result.value} ({dkim.selector}._domainkey.{dkim.domain})")DMARC (Domain-based Message Authentication)
Section titled “DMARC (Domain-based Message Authentication)”Checks that SPF or DKIM align with the From address and enforces the domain’s policy.
DMARC Result Structure
Section titled “DMARC Result Structure”from vaultsandbox.types import DMARCStatus, DMARCPolicy
dmarc = email.auth_results.dmarc
if dmarc: print(dmarc.result) # DMARCStatus.PASS, DMARCStatus.FAIL, etc. print(dmarc.domain) # Domain checked print(dmarc.policy) # DMARCPolicy.NONE, QUARANTINE, REJECT print(dmarc.aligned) # Whether SPF/DKIM aligns with From domainDMARC Status Values
Section titled “DMARC Status Values”| Status | Meaning |
|---|---|
PASS | DMARC check passed (SPF or DKIM aligned) |
FAIL | DMARC check failed |
NONE | No DMARC policy found |
SKIPPED | Check was skipped (see below) |
DMARC Policies
Section titled “DMARC Policies”| Policy | Meaning |
|---|---|
NONE | No action (monitoring only) |
QUARANTINE | Treat suspicious emails as spam |
REJECT | Reject emails that fail DMARC |
DMARC Example
Section titled “DMARC Example”from vaultsandbox.types import DMARCStatus
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
if email.auth_results.dmarc: dmarc = email.auth_results.dmarc
assert dmarc.result == DMARCStatus.PASS assert dmarc.domain == "example.com"
print(f"DMARC {dmarc.result.value} (policy: {dmarc.policy.value if dmarc.policy else 'none'})")Reverse DNS
Section titled “Reverse DNS”Verifies the sending server’s IP resolves to a hostname that matches the sending domain.
Reverse DNS Result Structure
Section titled “Reverse DNS Result Structure”from vaultsandbox.types import ReverseDNSStatus
reverse_dns = email.auth_results.reverse_dns
if reverse_dns: print(reverse_dns.result) # ReverseDNSStatus.PASS, FAIL, NONE, or SKIPPED print(reverse_dns.ip) # Server IP print(reverse_dns.hostname) # Resolved hostname (may be None)Reverse DNS Status Values
Section titled “Reverse DNS Status Values”| Status | Meaning |
|---|---|
PASS | IP resolves to hostname matching sender domain |
FAIL | IP doesn’t resolve or hostname doesn’t match |
NONE | No reverse DNS record found |
SKIPPED | Check was skipped (see below) |
Reverse DNS Example
Section titled “Reverse DNS Example”from vaultsandbox.types import ReverseDNSStatus
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
if email.auth_results.reverse_dns: rdns = email.auth_results.reverse_dns
print(f"Reverse DNS: {rdns.ip} → {rdns.hostname}") print(f"Result: {rdns.result.value}")
if rdns.result == ReverseDNSStatus.PASS: print("Reverse DNS verified")Migration Note
Section titled “Migration Note”Breaking Change in 0.7.0: The verified boolean field has been replaced with result string field.
Before (< 0.7.0):
if reverse_dns.verified: # True/False print("Verified")After (0.7.0+):
from vaultsandbox.types import ReverseDNSStatus
if reverse_dns.result == ReverseDNSStatus.PASS: print("Verified")Skipped Status
Section titled “Skipped Status”All authentication checks (SPF, DKIM, DMARC, Reverse DNS) can return a SKIPPED status, indicating the check was not performed.
When Skipped Appears
Section titled “When Skipped Appears”The skipped status appears when:
- Inbox has
email_auth: False- Authentication was disabled for this inbox - Server has globally disabled that check - Server configuration skips certain checks
- Master switch is off - Server has
VSB_EMAIL_AUTH_ENABLED=falseset
Handling Skipped Results
Section titled “Handling Skipped Results”from vaultsandbox.types import SPFStatus
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
if email.auth_results.spf: if email.auth_results.spf.result == SPFStatus.SKIPPED: print("SPF check was skipped for this inbox") elif email.auth_results.spf.result == SPFStatus.PASS: print("SPF passed")Validation with Skipped
Section titled “Validation with Skipped”The validate() method treats skipped as not a failure - it’s informational. When all checks are skipped, validation passes (since there are no failures).
# Inbox with email_auth=Falseinbox = await client.create_inbox(CreateInboxOptions(email_auth=False))
# ... receive email ...
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))validation = email.auth_results.validate()
# validation.passed will be True (no failures, just skipped)# validation.failures will be []Validation Helper
Section titled “Validation Helper”The validate() method provides a summary of all authentication checks. The skipped status is treated as passing (not a failure) - only explicit failures like FAIL are counted.
validation = email.auth_results.validate()
print(validation.passed) # True if SPF, DKIM, and DMARC all passedprint(validation.spf_passed) # Whether SPF explicitly passedprint(validation.dkim_passed) # Whether at least one DKIM signature passedprint(validation.dmarc_passed) # Whether DMARC explicitly passedprint(validation.reverse_dns_passed) # Whether reverse DNS passedprint(validation.failures) # List of failure reasonsAuthResultsValidation Structure
Section titled “AuthResultsValidation Structure”@dataclassclass AuthResultsValidation: passed: bool # True if SPF, DKIM, and DMARC all passed spf_passed: bool # Whether SPF check passed dkim_passed: bool # Whether at least one DKIM signature passed dmarc_passed: bool # Whether DMARC check passed reverse_dns_passed: bool # Whether reverse DNS check passed failures: list[str] # Array of failure descriptionsNote: The
passedfield reflects whether SPF, DKIM, and DMARC all passed. Reverse DNS is tracked separately inreverse_dns_passedbut does not affect the overallpassedstatus.
Validation Examples
Section titled “Validation Examples”All checks pass:
validation = email.auth_results.validate()
# AuthResultsValidation(# passed=True,# spf_passed=True,# dkim_passed=True,# dmarc_passed=True,# reverse_dns_passed=True,# failures=[]# )
assert validation.passed is Trueassert validation.spf_passed is Trueassert validation.dkim_passed is Trueassert validation.dmarc_passed is Trueassert validation.failures == []Some checks fail:
validation = email.auth_results.validate()
# AuthResultsValidation(# passed=False,# spf_passed=False,# dkim_passed=True,# dmarc_passed=False,# reverse_dns_passed=True,# failures=[# "SPF check failed: softfail (domain: example.com)",# "DMARC policy: fail (policy: reject)"# ]# )
if not validation.passed: print("Authentication failures:") for failure in validation.failures: print(f" - {failure}")
# Check individual results print(f"SPF: {'PASS' if validation.spf_passed else 'FAIL'}") print(f"DKIM: {'PASS' if validation.dkim_passed else 'FAIL'}") print(f"DMARC: {'PASS' if validation.dmarc_passed else 'FAIL'}") print(f"Reverse DNS: {'PASS' if validation.reverse_dns_passed else 'FAIL'}")Testing Patterns
Section titled “Testing Patterns”Strict Authentication
Section titled “Strict Authentication”import pytestfrom vaultsandbox import WaitForEmailOptions
@pytest.mark.asyncioasync def test_email_passes_all_authentication_checks(inbox): await send_email(inbox.email_address)
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000)) validation = email.auth_results.validate()
assert validation.passed is True assert validation.failures == []Lenient Authentication
Section titled “Lenient Authentication”import pytestfrom vaultsandbox import WaitForEmailOptionsfrom vaultsandbox.types import DKIMStatus
@pytest.mark.asyncioasync def test_email_has_valid_dkim_signature(inbox): await send_email(inbox.email_address)
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
# Only check DKIM (most reliable) assert email.auth_results.dkim is not None assert len(email.auth_results.dkim) > 0 assert email.auth_results.dkim[0].result == DKIMStatus.PASSHandling Missing Authentication
Section titled “Handling Missing Authentication”import pytestfrom vaultsandbox import WaitForEmailOptions
@pytest.mark.asyncioasync def test_handles_emails_without_authentication(inbox): email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
# Some senders don't have SPF/DKIM configured # Note: validate() requires explicit 'pass' status - 'none' is considered a failure validation = email.auth_results.validate()
# Check individual results print(f"SPF: {'PASS' if validation.spf_passed else 'FAIL'}") print(f"DKIM: {'PASS' if validation.dkim_passed else 'FAIL'}") print(f"DMARC: {'PASS' if validation.dmarc_passed else 'FAIL'}") print(f"Reverse DNS: {'PASS' if validation.reverse_dns_passed else 'FAIL'}")
# Log failures for debugging if not validation.passed: print(f"Auth failures (expected for test emails): {validation.failures}")Testing Specific Checks
Section titled “Testing Specific Checks”import pytestfrom vaultsandbox import WaitForEmailOptionsfrom vaultsandbox.types import SPFStatus, DKIMStatus, DMARCStatus
@pytest.fixtureasync def inbox(client): inbox = await client.create_inbox() yield inbox await inbox.delete()
@pytest.fixtureasync def email(inbox): await send_email(inbox.email_address) return await inbox.wait_for_email(WaitForEmailOptions(timeout=10000))
@pytest.mark.asyncioasync def test_spf_check(email): # For production emails, require explicit pass if email.auth_results.spf: assert email.auth_results.spf.result == SPFStatus.PASS
@pytest.mark.asyncioasync def test_dkim_check(email): # At least one DKIM signature must pass if email.auth_results.dkim: any_passed = any(d.result == DKIMStatus.PASS for d in email.auth_results.dkim) assert any_passed is True
@pytest.mark.asyncioasync def test_dmarc_check(email): # For production emails, require explicit pass if email.auth_results.dmarc: assert email.auth_results.dmarc.result == DMARCStatus.PASSWhy Authentication Matters
Section titled “Why Authentication Matters”Production Readiness
Section titled “Production Readiness”Testing authentication catches issues like:
- Misconfigured SPF records → emails rejected by Gmail/Outlook
- Missing DKIM signatures → reduced deliverability
- DMARC failures → emails sent to spam
- Reverse DNS mismatches → flagged as suspicious
Real-World Example
Section titled “Real-World Example”import pytestfrom vaultsandbox import WaitForEmailOptions
@pytest.mark.asyncioasync def test_production_email_configuration(inbox, app): await app.send_welcome_email(inbox.email_address)
email = await inbox.wait_for_email(WaitForEmailOptions(timeout=10000)) validation = email.auth_results.validate()
# In production, these should all pass if not validation.passed: print("Email authentication issues detected:") for failure in validation.failures: print(f" {failure}") print("") print("Action required:")
if any("SPF" in f for f in validation.failures): print("- Fix SPF record for your domain") if any("DKIM" in f for f in validation.failures): print("- Configure DKIM signing in your email service") if any("DMARC" in f for f in validation.failures): print("- Add/fix DMARC policy")
# Fail test if authentication fails assert validation.passed is TrueTroubleshooting
Section titled “Troubleshooting”No Authentication Results
Section titled “No Authentication Results”auth = email.auth_results
if not auth.spf and not auth.dkim and not auth.dmarc: print("No authentication performed") print("This may happen for:") print("- Emails sent from localhost/internal servers") print("- Test SMTP servers without authentication")All Checks Fail
Section titled “All Checks Fail”validation = email.auth_results.validate()
if not validation.passed: print(f"Authentication failed: {validation.failures}")
# Common causes: # 1. No SPF record: Add "v=spf1 ip4:YOUR_IP -all" to DNS # 2. No DKIM: Configure your mail server to sign emails # 3. No DMARC: Add "v=DMARC1; p=none" to DNS # 4. Wrong IP: Update SPF record with correct server IPUnderstanding Failure Reasons
Section titled “Understanding Failure Reasons”validation = email.auth_results.validate()
for failure in validation.failures: if "SPF" in failure: print("Fix SPF: Update DNS TXT record for your domain") if "DKIM" in failure: print("Fix DKIM: Enable DKIM signing in your email service") if "DMARC" in failure: print("Fix DMARC: Add DMARC policy to DNS")Next Steps
Section titled “Next Steps”- Email Authentication Guide - Testing authentication in depth
- Spam Analysis - Spam detection and scoring
- Email Objects - Understanding email structure
- Testing Patterns - Real-world testing examples
- Gateway Security - Understanding the security model