Install Postfix from your distribution and accept the defaults, and you have a mail server that sends your mail in the clear to any server that does not offer TLS, and takes mail from anybody claiming to be anybody. Debian’s shipped main.cf sets both TLS levels to may. Nothing checks SPF. Nothing checks DKIM. A sender domain that does not exist is accepted.
That is not a bug. It is what the standards asked for twenty years ago, and it is how a great many mail servers on the internet still run today.
This post builds the other kind. It takes the house rules from the lead post in this series and puts every one of them into a working Postfix, with the reply each one produces when it refuses something. The argument for the rules lives in that post and I will not make it twice. This one is the build.
| Rule | Debian’s Postfix as shipped | This build | What a breach gets |
|---|---|---|---|
| No plaintext in | TLS offered, never required | encrypt on every listener | 530 5.7.0 Must issue a STARTTLS command first |
| No plaintext out | TLS if offered, clear text if not | encrypt, DANE or MTA-STS where published | deferred, dsn=4.7.4 or dsn=4.7.5 |
| IPv6 on everything | inet_protocols = all | the same, plus AAAA on every MX | every test below ran on both families |
| Relay only with a login | relays for mynetworks | port 25 never relays, 587 and 465 need a login | 554 5.7.1 … Relay access denied |
| A login sends as itself | not checked | envelope and From: both tied to the login | 553 5.7.1 … not owned by user |
| Sender domain must exist | not checked | reject_unknown_sender_domain, code 550 | 550 5.1.8 … Domain not found |
| SPF fail, DKIM fail | not checked | Rspamd, with forced rejects | 554 5.7.1 SPF hard fail / DKIM signature does not verify |
Every reply in this post was captured from a running server, over IPv4 and IPv6. Several settings turned out to do nothing until something else was also set. I found each of those by watching it do nothing. They are called out below, because they are the ones that will bite you.
The Lab
| Part | Version | Job |
|---|---|---|
| Postfix | 3.10.13, Debian trixie | the MX and submission server for alpha.test |
| Dovecot | 2.4.1 | passwords for logins, every mail protocol off |
| Rspamd, Redis | 3.12.1, 8.0.2 | SPF, DKIM, DMARC, DKIM signing |
| postfix-tlspol | 1.14.4 | outbound policy from TLSA and MTA-STS |
| BIND, unbound | 9.20.30, 1.26.1 | a signed private test. zone, and a resolver that validates it |
| Domain | What it is for |
|---|---|
alpha.test | the server this post builds |
bravo.test | Stalwart, which gets its own post |
plain.test | the bad neighbour: unsigned zone, no STARTTLS |
sender.test | SPF -all, DKIM, DMARC p=reject: for forging, tampering and forwarding |
Every listener binds both families, and every MX has an A and an AAAA record. IPv6 is not an extra here. A mail server without it is a defect, and proving this one has it is simple: run every check twice.
Stage By Stage
An SMTP session is a fixed sequence, and every rule in this build has a natural place in it. The order matters for one reason: refuse as early as the evidence allows. A sender domain that does not exist is known at MAIL FROM, so there is no reason to take the body before refusing it. A tampered body is only known after DATA, so that is where it gets refused.
The rest of the post goes through those stages in order: TLS in, TLS out, logins and relaying, sender addresses, then what Rspamd does with the body.
No Plaintext Coming In
One line:
smtpd_tls_security_level = encrypt
A client that tries to skip STARTTLS gets nothing but the refusal:
<- 220 mail.alpha.test ESMTP Postfix
-> MAIL FROM:<carol@sender.test>
<** 530 5.7.0 Must issue a STARTTLS command first
Now read what Postfix’s own manual says about that setting:
According to RFC 2487 this MUST NOT be applied in case of a publicly-referenced SMTP server. Instead, this should be used on dedicated servers, for example submission (port 587).1
RFC 2487 is the old number for RFC 3207, and that rule is why most MX hosts on the internet still accept mail in the clear. I am applying it to a publicly referenced MX anyway. On purpose. The lead post explains why I think the rule is wrong, and the cost is real: a sender whose server cannot do TLS cannot reach you. In 2026 that is a server you do not want mail from.
No Plaintext Going Out
This is the half most guides get wrong, because the obvious setting does not do what its name suggests.
Postfix has a dane security level. Set it and you would expect mail to go out authenticated. It does not. Here is what postconf(5) says it does when the far end publishes nothing:
When DNSSEC-validated TLSA records are not found the effective tls security level is “may”.2
may is opportunistic TLS, which the TLS_README describes plainly: “Otherwise, messages are sent in the clear.”3 Most of the internet publishes no TLSA records. So smtp_tls_security_level = dane on its own means DANE for the handful of domains that do it, and clear text for everybody else whose server does not offer STARTTLS. Opportunistic, with a better name.
The fix is to make encrypt the floor and lift each destination above it when it publishes something better. postfix-tlspol does the lifting. It is a small policy server, MIT licensed, that Postfix asks about every recipient domain over a socketmap. It checks for usable TLSA records in a signed zone first and MTA-STS second, and answers with the policy Postfix should use:
smtp_dns_support_level = dnssec
smtp_tls_security_level = encrypt
smtp_tls_mandatory_protocols = >=TLSv1.2
smtp_tls_policy_maps = socketmap:inet:[fd89:50::25]:8642:QUERYwithTLSRPT
Its README is specific about what it returns: it will “Verify TLSA records for correctness and supported parameters, only then the dane-only policy (Mandatory DANE) will be returned.”4 Asked about the three lab domains, it said exactly that:
level=INFO msg="Evaluated policy" origin=network domain=alpha.test policy=dane-only ttl=214
level=INFO msg="Evaluated policy" origin=network domain=bravo.test policy=dane-only ttl=198
level=INFO msg="No policy found" origin=network domain=plain.test ttl=1800
No policy for plain.test means Postfix falls back to the encrypt floor, and plain.test offers no STARTTLS. The mail waits:
postfix/smtp[194]: 78598130DA54: to=<someone@plain.test>, relay=mail.plain.test[fd89:50::22]:25,
delay=0.02, delays=0.01/0.01/0.01/0, dsn=4.7.4,
status=deferred (TLS is required, but was not offered by host mail.plain.test[fd89:50::22])
And the case that proves DANE is doing something. Early in the build, Stalwart on bravo.test was still serving its own bootstrap certificate while the zone published the TLSA record for the real one. A server with a perfectly good certificate, just not the one the domain owner vouched for:
postfix/smtp[196]: server certificate verification failed for mail.bravo.test[fd89:50::21]:25:
num=65:no matching DANE TLSA records
postfix/smtp[196]: 79160130DA56: to=<someone@bravo.test>, relay=mail.bravo.test[10.89.50.21]:25,
delay=5.9, delays=0/0.01/5.9/0, dsn=4.7.5, status=deferred (Server certificate not verified)
Deferred is the right answer in both cases. A deferred message is retried for days, which leaves the other side time to fix their end, and nothing is lost. A message sent in the clear cannot be unsent. As such, waiting is always the cheaper failure.
The resolver is part of the trust chain
smtp_dns_support_level = dnssec makes Postfix ask its resolver for validated answers, and then believe the resolver’s AD bit, the flag that says “I checked the signatures”. Postfix validates nothing itself. It trusts. Whatever resolver is named in /etc/resolv.conf is now deciding whether your mail is authenticated.
You will find advice saying glibc throws that bit away unless resolv.conf carries options trust-ad. I tested it, and on this Postfix it makes no difference: DANE matched with the option and without it. The source explains why. Postfix sets the flag itself whenever it asks for DNSSEC:
if (DNS_WANT_DNSSEC_VALIDATION(flags))
flags |= (RES_USE_EDNS0 | RES_TRUSTAD);
That is from src/dns/dns_lookup.c.5 So what matters is not a resolver option. It is which resolver you point at. It must validate, and the path to it must be one you trust, which in practice means it runs on the mail server itself or on a network nobody else can reach. A public resolver across the internet will happily set AD for you. So will anybody sitting between you and it.
DANE: Publish The Key, Not The Certificate
A TLSA record pins what a sending server should see when it connects. The one to use is 3 1 1, and each of the three numbers is a choice:
| Field | Value | Meaning | Why this one |
|---|---|---|---|
| Usage | 3, DANE-EE | match the server’s own certificate | no CA in the chain to trust or to fail |
| Selector | 1, SPKI | hash the public key only | survives every renewal on the same key |
| Matching | 1, SHA-256 | a digest, not the full key | every DANE client must support it |
RFC 7672 gives the reason for that combination in one line:
this record type works for all clients and need not change across certificate renewals with the same key.6
The hash is one pipeline, and here it is against the lab certificate and the record the signed zone serves:
$ openssl x509 -in mail.alpha.test.crt -noout -pubkey \
| openssl pkey -pubin -outform DER | sha256sum
093a80cac907a52eb41d17df20163c95f2e15d6702c76f65174d7fc32460c69f -
$ dig +dnssec _25._tcp.mail.alpha.test TLSA
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
_25._tcp.mail.alpha.test. 165 IN TLSA 3 1 1 093A80CAC907A52EB41D17DF20163C95F2E15D6702C76F65174D7FC3 2460C69F
The lab proved the “need not change” part by accident. Halfway through, Python 3.13’s stricter certificate checks refused the lab certificates for missing an authority key identifier, so every one of them was reissued. Same private keys, new certificates. Not one TLSA record changed, and not one DANE sender noticed.
Now the trap. Most people renew their certificates with certbot, automatically, every few weeks. From certbot’s own documentation:
Not reusing private keys is the default behavior of Certbot.7
So the default renewal makes a new key every time. The TLSA record stops matching, and every sender that checks DANE starts deferring your mail on renewal day. Nothing on your side breaks. Nothing tells you. Run certbot with --reuse-key and the record outlives every certificate you put behind it. When the key does have to change, there is an order to it:
Step two is the one people skip. The result is deferred mail until the old record ages out of every cache, and it comes from exactly the senders who are doing security properly.
Nobody Relays Without A Login
Relaying is sending mail onwards to a domain you are not the destination for. An open relay is how spam gets laundered through somebody else’s reputation. The old way of closing one was to trust an address range, so anything from the office network could relay. That is trust by location. On a network with guest WiFi, VPN users and the odd compromised laptop, location is worth very little.
So no address is trusted except the machine itself:
mynetworks = 127.0.0.0/8 [::1]/128
smtpd_relay_restrictions = reject_unauth_destination
Port 25 is for mail arriving for this domain and nothing else. It never relays, for anybody:
<** 554 5.7.1 <someone@plain.test>: Relay access denied
Users send through submission: port 587 with STARTTLS, or 465 with TLS from the first byte. Both want a login before they will do anything. Dovecot checks the passwords, with every mail protocol switched off so it does nothing else, and Postfix asks it through a socket. The submission listener in master.cf:
submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_tls_auth_only=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_relay_restrictions=permit_sasl_authenticated,reject
smtpd_tls_auth_only means the server does not even offer AUTH until TLS is up, so a password never crosses the wire in the clear. AUTH lines in the reply to a plaintext EHLO: zero. A client that connects to 587 and tries to send without logging in gets nowhere:
<** 554 5.7.1 <unknown[fd89:50::50]>: Client host rejected: Access denied
A Login Owns Its Addresses
A login proves who you are. It says nothing about which addresses you may send as. Without that link, any user on the server can send as the finance director, and the mail will be signed and delivered as if it were real. Postfix ties the two together with a map of address to owner:
alice@alpha.test alice@alpha.test
bob@alpha.test bob@alpha.test
postmaster@alpha.test alice@alpha.test
reject_sender_login_mismatch on the submission ports enforces it against the envelope sender. Logged in as alice, trying to send as bob:
<** 553 5.7.1 <bob@alpha.test>: Sender address rejected: not owned by user alice@alpha.test
That covers the envelope. It does not cover the From: header, which is the address the recipient actually sees, and postconf(5) says so in its notes on this very restriction:
to enforce that the From: header address matches the envelope sender (MAIL FROM) address, use an external filter such as a Milter8
Rspamd is already the milter here, so the header check is a short Lua rule that compares the logged-in user with the From: address, plus a forced reject on the symbol it raises:
rspamd_config.AUTH_FROM_NOT_LOGIN = {
callback = function(task)
local user = task:get_user()
if not user then return false end
local from = task:get_from('mime')
if not from or not from[1] or not from[1].addr then return true, 'no From address' end
if string.lower(from[1].addr) ~= string.lower(user) then
return true, from[1].addr .. ' is not ' .. user
end
return false
end,
score = 0.0,
description = 'Authenticated user sent with a From: address that is not their own',
group = 'headers',
}
Alice sending with MAIL FROM:<alice@alpha.test> and From: bob@alpha.test:
<** 554 5.7.1 You may only send as your own address
A display name is fine and so is a change of case. Alice <ALICE@alpha.test> goes through, because the comparison is on the address and ignores case.
The check that switches itself off
Here is the first of the settings that silently do nothing. The obvious way to stop outsiders forging your own domain on port 25 is to put the login-mismatch checks there too. Postfix accepts the configuration, starts, and logs this:
warning: restriction `reject_authenticated_sender_login_mismatch' ignored: no SASL support
warning: restriction `reject_unauthenticated_sender_login_mismatch' ignored: no SASL support
Port 25 has SASL switched off, quite rightly, because nobody logs in there. So the check is skipped. The mail is accepted. The only evidence is a warning in a log nobody reads, and with the check gone, anybody on the internet could send MAIL FROM:<alice@alpha.test> to this server and have it delivered to alice’s colleagues as if she had written it.
The fix is to refuse our own domains by name on port 25, because mail from us never legitimately arrives there:
smtpd_sender_restrictions = reject_non_fqdn_sender, reject_unknown_sender_domain,
check_sender_access texthash:/etc/postfix/own_domains
with own_domains holding one line, alpha.test REJECT Our own domain: send through submission with a login. Tested from outside:
<** 554 5.7.1 <alice@alpha.test>: Sender address rejected: Our own domain: send through submission with a login
Sender Domains That Do Not Exist
If the domain in MAIL FROM does not exist, nothing can ever be sent back to it, and nothing legitimate ever came from it. reject_unknown_sender_domain refuses mail when that domain has “no DNS MX and no DNS A record”.9 Postfix’s default reply code for it is 450, a temporary failure, which invites the forger to try again later. I changed it to 550.
postconf(5) is blunt about that setting: “Do not change this unless you have a complete understanding of RFC 5321.”10 Fair enough. Here is the reasoning. A 4xx tells the sender to queue and retry, which is right when the answer might change. A domain with no records now is not going to grow some in the next five minutes. Postfix separates that from the case where the answer is not known yet: a DNS failure is handled by unknown_address_tempfail_action, which still defers. Changing the code changes only the case that has an answer.
The lab tested all three, plus the one the IPv6 rule cares about. The postconf wording says “A record”, and a domain whose only address is an AAAA record should not be treated as missing:
| Sender domain | DNS says | Reply |
|---|---|---|
nosuch-lab.co.uk | NXDOMAIN | 550 5.1.8 <someone@nosuch-lab.co.uk>: Sender address rejected: Domain not found |
v6only-lab.co.uk | AAAA only, no A, no MX | accepted |
dnsfail-lab.co.uk | SERVFAIL | 450 4.1.8 … Sender address rejected: Domain not found, retried later |
sender.test | MX, A, AAAA | accepted |
In practice “A record” covers AAAA, and an IPv6-only domain is safe. The refusal comes at MAIL FROM, before the sender has sent a single byte of the body. Postfix holds a MAIL FROM rejection until the first RCPT TO by default, so the reply arrives one step later than you might expect, which is why the stage diagram shows it there.
SPF, DKIM And DMARC, With Teeth
These three are covered properly in the lead post. In one line each: SPF says which servers may send for a domain, DKIM signs the message so changes show, and DMARC says what to do when they fail and whether they match the visible From: domain. The house rule is to reject on an SPF hard fail and on a DKIM signature that is present and does not verify. Unsigned mail is not a DKIM failure. DMARC p=reject on the sender’s domain is what handles that.
Rspamd does all three in one milter, plus DKIM signing for mail going out. Postfix hands it every message, and if Rspamd is not answering, mail waits rather than going through unchecked:
smtpd_milters = inet:127.0.0.1:11332
non_smtpd_milters = $smtpd_milters
milter_default_action = tempfail
Rspamd works on scores, and that is the first thing to understand about it. Out of the box, nothing is rejected because SPF failed. Nothing. Each result adds or subtracts a few points, and the message is rejected when the total passes 15. The force_actions module is what turns a single result into a decision:
rules {
SPF_FAIL_REJECT {
action = "reject";
expression = "R_SPF_FAIL";
message = "SPF hard fail: this host is not allowed to send for that domain";
}
DKIM_FAIL_REJECT {
action = "reject";
expression = "R_DKIM_REJECT";
message = "DKIM signature does not verify: the message was changed or forged";
}
DMARC_REJECT {
action = "reject";
expression = "DMARC_POLICY_REJECT";
message = "DMARC policy of the sender's domain says reject";
}
}
Five messages from sender.test, sent from the client in the lab. The SPF record allows ip6:fd89:50::50 and nothing else, so the same client sending over IPv4 is an unauthorised server for that domain. That is also exactly what a forwarded message looks like:
| Case | Sent as | SPF | DKIM | DMARC | Rspamd score | Reply |
|---|---|---|---|---|---|---|
| A | signed, from the listed IPv6 address | pass | pass | pass | 3.59 of 15 | accepted |
| B | signed, then the bank details changed | pass | fail | pass | 2.30 of 15 | 554 5.7.1 DKIM signature does not verify |
| C | unsigned, from IPv4 | fail | none | reject | 5.00 of 15 | 554 5.7.1 SPF hard fail |
| D | signed and intact, from IPv4 | fail | pass | pass | 2.30 of 15 | 554 5.7.1 SPF hard fail |
| E | unsigned, from the listed IPv6 address | pass | none | pass | 3.80 of 15 | accepted |
Look at case B. It is an invoice with the sort code and account number changed after it was signed. Invoice fraud, in other words. SPF passed, because it came from the right server. DMARC passed, because SPF passed and lined up with the From: domain. On its scores Rspamd gave it 2.30 out of 15 and would have delivered it. The only thing that stopped it was the forced reject on the broken signature. DMARC on its own lets a tampered message through as long as one of the two checks survives, and that is worth knowing before you call a domain protected because its DMARC says reject.
Case D is the cost, and it should be said plainly. A correctly signed, untouched message is refused because it came from a server not in the SPF record. That is what forwarding does: the forwarder sends your message on from its own address. Rejecting on SPF alone means forwarded mail from domains with -all gets bounced. SRS on the forwarder fixes it, and so would trusting DKIM over SPF, but the house rule is the house rule, and the cost goes in writing.
The default that trusts every private network
The second setting that silently does nothing. Rspamd skips SPF and some other checks for addresses it considers local, and the shipped list in options.inc is this:
local_addrs = [192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12, fd00::/8, 169.254.0.0/16, fe80::/10];
Every private range, both families. If your MX sits behind a load balancer, a NAT or a proxy on a private address, SPF is quietly skipped for all of it. In the lab, which is all private addressing, SPF never failed until this was changed.
The obvious fix does not work. Rspamd has two directories for local settings, and its FAQ spells out the difference: local.d “Merges with defaults. Collections and lists are combined”, while override.d “Replaces defaults.”11 A narrower list in local.d/options.inc is combined with the shipped one, which means it narrows nothing. It has to go in override.d/options.inc:
local_addrs = [127.0.0.0/8, ::1];
and rspamadm configdump options is how you check what Rspamd actually ended up with, rather than what you think you wrote.
Greylisting is also switched off in this build. It sends a temporary rejection to unknown senders and accepts them on the retry, which does stop some spam, but Rspamd’s greylisting answers before SPF and DMARC have had their say, so while testing you see a deferral where you expected a verdict.12 The tests above are only meaningful with it off.
Your own bounces, refused by your own DMARC
The last trap turned up when Stalwart in the lab refused a bounce from this server. Postfix sends bounces with an empty envelope sender and From: MAILER-DAEMON@mail.alpha.test. With no envelope sender, the only SPF check left is on the name the server gave in HELO, mail.alpha.test, which had no SPF record. The bounce was not signed either. So DMARC fell back to the policy for alpha.test, found p=reject, and the receiving server did exactly what the domain told it to:
smtp.dmarc-fail … domain = "alpha.test", policy = "reject", result = DMARC check failed
(dmarc.fail) { causedBy = Policy not aligned }
The domain’s own policy was throwing away its own delivery failures. Every MX host name gets its own SPF record, which fixes it:
mail IN TXT "v=spf1 a -all"
Six Settings That Look Done
The traps above share a shape. Every one of them is a setting that looks done, produces no error, and does nowt.
| Looks done | What actually happens | Found by |
|---|---|---|
smtp_tls_security_level = dane | clear text wherever no TLSA is published | reading postconf(5) |
| login-mismatch checks on port 25 | skipped, one warning in the log | sending a forgery |
local_addrs narrowed in local.d | merged with the default, nothing narrowed | rspamadm configdump |
DMARC p=reject on the sender | tampered mail passes if SPF passes | case B |
| certbot renewals | new key, TLSA stops matching | reading certbot’s docs |
DMARC p=reject on your own domain | your own bounces refused | a refused bounce |
Two of them are in the documentation, if you read the right paragraph of the right page. The other four turned up only by sending the message the rule was supposed to stop and looking at what came back. Reading the configuration back and agreeing with it found none of them. If you take one habit from this post, take that one, because every line here can be copied into a server and still not be working.
The Defaults Are Somebody Else’s Decision
Every refusal in this post is a setting Postfix has had for years. Even DANE, the newest thing here, has been in it since 2.11.13 None of it is new, none of it is expensive, and the server underneath is the same free software it always was. The gap between a mail server that lets an altered invoice through and one that refuses it at DATA is a page of configuration and an afternoon of testing.
So why does so little of the internet run like this? Because the defaults decide, and the defaults were set to make sure mail got through on an internet where TLS was the exception. They were a reasonable call then. Leave them alone now and you have handed your security decisions to a choice somebody made in 2002, for a network that no longer exists, about mail that was not yours.
Running a mail server is a decision to be responsible for what passes through it. Those settings are the decision written down. The defaults are what you get when nobody made it.
postconf(5): smtpd_tls_security_level — the
encryptlevel: “According to RFC 2487 this MUST NOT be applied in case of a publicly-referenced SMTP server.” ↩︎postconf(5): smtp_tls_security_level — the
danelevel: “When DNSSEC-validated TLSA records are not found the effective tls security level is “may”.” ↩︎Postfix TLS_README: Opportunistic TLS — “The SMTP transaction is encrypted if the STARTTLS ESMTP feature is supported by the server. Otherwise, messages are sent in the clear.” ↩︎
postfix-tlspol on GitHub — MIT licensed MTA-STS and DANE policy server for Postfix: “Verify TLSA records for correctness and supported parameters, only then the dane-only policy (Mandatory DANE) will be returned.” Version 1.14.4 used here. ↩︎
Postfix source, src/dns/dns_lookup.c — “Automatically turns on the RES_TRUSTAD flag on systems that support this flag”, and the code that does it when DNSSEC validation is requested. ↩︎
RFC 7672 §3.1.1 — SMTP security via opportunistic DANE TLS. TLSA records for SMTP servers “should, in most cases, be “DANE-EE(3) SPKI(1) SHA2-256(1)” records”. ↩︎
Certbot user guide, command-line options —
--reuse-key: “When renewing, use the same private key as the existing certificate. (default: False)”, and under--no-reuse-key: “Not reusing private keys is the default behavior of Certbot.” ↩︎postconf(5): reject_known_sender_login_mismatch — “to enforce that the From: header address matches the envelope sender (MAIL FROM) address, use an external filter such as a Milter”. ↩︎
postconf(5): reject_unknown_sender_domain — rejects when the MAIL FROM domain has “no DNS MX and no DNS A record”, or a malformed MX. ↩︎
postconf(5): unknown_address_reject_code — default 450: “Do not change this unless you have a complete understanding of RFC 5321.” ↩︎
Rspamd FAQ: the difference between local.d and override.d — local.d “Merges with defaults. Collections and lists are combined”; override.d “Replaces defaults. Entire sections are overwritten.” ↩︎
Rspamd greylisting module — “The greylisting module triggers a soft reject action, which is intended to be interpreted by the MTA as a temporary rejection”. ↩︎
Postfix TLS_README: DANE — “Postfix ≥ 2.11 supports the new dane and dane-only security levels”. ↩︎