Email authentication is a fundamental layer of domain security and brand protection. Without strict DNS-level verification policies, malicious actors can easily spoof your domain address in phishing campaigns.
In this post, we explore how to configure a hardened email security posture for teamsehem.se, how DKIM asymmetric keys work, and how we automate DNS verification using Pytest and a custom C# .NET Model Context Protocol (MCP) tool.
1. The Core Email Authentication Pillars
SPF (Sender Policy Framework)
SPF specifies which mail servers are authorized to send outgoing email on behalf of your domain.
v=spf1 mx a -all
v=spf1: Identifies the SPF version.mx a: Authorizes servers matching the domain’s MX and A records (e.g.,mail.teamsehem.se).-all(Hard Fail): Instructs receiving servers to explicitly reject emails coming from any IP address not listed.
DKIM (DomainKeys Identified Mail)
DKIM uses asymmetric cryptography (Public/Private RSA keypair) to sign and verify emails:
- Private Key Location & Security Model:
- Server Filesystem: Stored strictly at
/etc/virtual/teamsehem.se/dkim.private.key(accessible only via Root/Admin SSH with0600file permissions). - DirectAdmin Evolution UI (
/evo/dns-records): Control panel skins intentionally hide raw private keys from web users to prevent credential theft. The Exim mail daemon automatically reads the private key from disk when signing outgoing mail.
- Server Filesystem: Stored strictly at
- Public Key: Published openly in DNS as a TXT record at
x._domainkey.teamsehem.se:
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwW3NDO...
When a receiving mail provider (like Gmail or Proton) receives your email, it queries DNS for x._domainkey.teamsehem.se and uses the public key to verify that the message was genuinely signed by your private key and was not modified in transit.
DMARC (Domain-based Message Authentication, Reporting & Conformance)
DMARC dictates what receiving servers must do if an incoming email fails SPF or DKIM checks.
v=DMARC1; p=reject; sp=reject
p=reject: Instructs mail servers to block and drop spoofed emails targeting@teamsehem.seimmediately, preventing them from even reaching a user’s Spam folder.sp=reject: Extends the exact samerejectpolicy to all subdomains (e.g.@sub.teamsehem.se), closing subdomain spoofing loopholes.
2. Automated Verification with Pytest
To prevent silent DNS regressions during nameserver or host migrations, we maintain an automated test suite in Pytest (tests/test_mail_dns.py).
The script queries authoritative nameservers (ns1.inleed.net through ns6.inleed.net) using dig and validates record alignment:
# tests/test_mail_dns.py
import subprocess
import pytest
DOMAIN = "teamsehem.se"
NAMESERVERS = [f"ns{i}.inleed.net" for i in range(1, 7)]
EXPECTED = {
"MX": {"name": DOMAIN, "type": "MX", "expected": "10 mail.teamsehem.se."},
"SPF": {"name": DOMAIN, "type": "TXT", "expected": "v=spf1 mx a -all"},
"DMARC": {"name": f"_dmarc.{DOMAIN}", "type": "TXT", "expected": "v=DMARC1; p=reject; sp=reject"},
"DKIM_TXT": {"name": f"x._domainkey.{DOMAIN}", "type": "TXT", "expected_partial": "v=DKIM1; k=rsa;"},
}
def resolve_record(ns, name, rtype):
cmd = ["dig", f"@{ns}", name, rtype, "+short", "+time=3", "+tries=3"]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=12)
return result.stdout.strip().split('\n')
except subprocess.TimeoutExpired:
return []
@pytest.mark.parametrize("record_key, config", EXPECTED.items())
@pytest.mark.parametrize("ns", NAMESERVERS)
def test_mail_dns_record(ns, record_key, config):
records = resolve_record(ns, config["name"], config["type"])
found = any(config.get("expected") in r or config.get("expected_partial") in r for r in records if r)
assert found
Running the test suite yields 28 passed assertions in under 5 seconds:
python3 -m pytest tests/test_mail_dns.py -v
# 28 passed in 3.95s
3. Integration with C# .NET MCP Tooling
To allow autonomous AI coding agents to verify mail DNS status on demand, we expose a tool via a C# .NET Model Context Protocol (MCP) server (MailDnsTools.cs).
The MCP server runs the Pytest verification in the background and transforms raw terminal output into structured JSON for agent reasoning:
[McpServerTool(Name = "check_mail_dns")]
[Description("Runs pytest test_mail_dns.py and returns structured per-record DNS results.")]
public string CheckMailDns()
{
var result = ProcessRunner.Run("python3", "-m pytest tests/test_mail_dns.py -v");
return ParseVerboseOutput(result);
}
This pattern ensures AI agents can verify live DNS security configurations without parsing raw shell streams or leaking sensitive credentials.