Get started
Email

Configuring PTR, DNSSEC, and Custom MTA Headers for 100% Inbox Placement

Reverse DNS, SPF, DKIM, DMARC, DNSSEC, MTA-STS and clean message headers: the records and settings that remove the technical reasons receivers reject or filter your mail.

Mail that lands in spam is rarely one big mistake. More often it is a sending address with no reverse DNS, an SPF record one lookup over the limit, a DKIM key nobody rotated, or a header that leaks an internal hostname. Receivers such as Gmail and Yahoo now publish firm requirements for much of this, and they reject mail that misses them.

To be plain about the title: no configuration can guarantee inbox placement. Receivers decide placement from your sending reputation, your recipients' engagement and complaints, and your content, and none of that lives in DNS. What the steps below do is remove the technical reasons mail gets rejected or filtered, so that reputation and content are the only things left to judge. The examples use example.com, a sending server at 192.0.2.25 and 2001:db8::25, and a third-party sender at spf.esp.example.

Key takeaways

  • Every sending IP needs forward-confirmed reverse DNS: a PTR record whose name resolves back to the same IP, and a matching EHLO name.
  • Publish SPF and DKIM, and make at least one align with your From domain. That alignment is what DMARC checks.
  • Move DMARC from p=none to quarantine to reject while reading aggregate reports at each step.
  • DNSSEC protects your records; a careless key rollover takes your whole domain offline for validating resolvers.
  • Bulk mail needs RFC 8058 one-click unsubscribe, and both unsubscribe headers must be covered by your DKIM signature.

Reverse DNS and FCrDNS

A receiver that accepts a connection from 192.0.2.25 looks up its PTR record, then looks up the A record of the name it gets back. If both agree, the IP has forward-confirmed reverse DNS (FCrDNS). Gmail requires valid forward and reverse DNS for every sender, not just bulk senders.

dig +short -x 192.0.2.25         # expect: mail.example.com.
dig +short A mail.example.com    # expect: 192.0.2.25
dig +short -x 2001:db8::25

PTR records live in the reverse zone of whoever controls the IP address, which is usually your hosting or network provider, not your own DNS zone. In the provider's reverse zone the record looks like this:

$ORIGIN 2.0.192.in-addr.arpa.
25  IN PTR mail.example.com.

For IPv6 the name is the address's 32 nibbles reversed, so 2001:db8::25 becomes 5.2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. If you send over IPv6, it needs its own PTR; receivers apply the same checks.

Three rules: use a name in a domain you control, not a generic host-192-0-2-25.provider.example; set your MTA's EHLO name to that same name (in Postfix, myhostname = mail.example.com); and make sure the name has exactly the matching A or AAAA record.

SPF

SPF lists the hosts allowed to send for the domain in the envelope sender (the Return-Path), which is not necessarily the From address people see.

example.com.  IN TXT "v=spf1 ip4:192.0.2.25 ip6:2001:db8::25 include:spf.esp.example -all"
  • Publish exactly one SPF record per name. Two v=spf1 records are a permanent error.
  • Stay within 10 DNS lookups. Each include, a, mx, exists and redirect counts, including those inside included records. ip4 and ip6 do not count.
  • End with -all once you are sure the list is complete, or ~all while you are still finding senders. Under DMARC enforcement, the DMARC policy matters more than the difference between the two.

DKIM

DKIM signs selected headers and the body with a private key; receivers fetch the public key from selector._domainkey.domain.

Keys

Use 2048-bit RSA. RFC 8301 sets 1024 bits as the minimum for signers and recommends 2048. Ed25519 signatures (RFC 8463) are smaller, but not every receiver verifies them yet, so sign with Ed25519 only in addition to RSA. Generate a key with OpenSSL:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out s202610.private
openssl pkey -in s202610.private -pubout -outform DER | openssl base64 -A

The public key is about 392 characters, longer than the 255-character limit on a single TXT string, so split it into several quoted strings in one record (shortened here). Receivers join them with no separator.

s202610._domainkey.example.com. IN TXT ( "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
                                         "...IDAQAB" )

Most DNS control panels do the splitting for you if you paste the whole value. Sign at least From, To, Subject, Date, Message-ID, MIME-Version, Content-Type, List-Unsubscribe and List-Unsubscribe-Post, and list From twice in the signer's header list (oversigning) so that nobody can add a second From header without breaking the signature. Do not use the l= body-length tag: it lets content be appended to signed mail.

Selector rotation

Put a date in the selector so rotation is routine; many operators rotate every six to twelve months.

  1. Generate a new key and publish it under a new selector, such as s202610.
  2. Wait for the record's TTL to pass and confirm with dig +short TXT s202610._domainkey.example.com.
  3. Switch the signer to the new selector and confirm dkim=pass with s=s202610 on a test message.
  4. Keep the old selector published for a week or more, because queued and forwarded mail is still verified against it.
  5. Revoke the old key by publishing an empty key, "v=DKIM1; k=rsa; p=", and delete the record later.

DMARC

DMARC ties SPF and DKIM to the From domain. A message passes when SPF or DKIM passes and the passing domain aligns with the From domain: the same organizational domain in relaxed mode, the default, or the exact domain in strict mode.

Start by observing:

_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:[email protected]"

Aggregate reports (rua) arrive daily as XML from large receivers, listing every IP that sent as your domain and whether it passed. Use them to find every legitimate sender, such as your CRM, helpdesk and invoicing system, and fix each one's alignment, usually by giving it a custom return-path domain or letting it sign DKIM as example.com. Then tighten in stages, giving each a few weeks of clean reports:

_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]"
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; sp=reject; rua=mailto:[email protected]"

sp sets the policy for subdomains. If reports go to a different domain, that domain must authorize it with a record such as example.com._report._dmarc.reports.example.net. IN TXT "v=DMARC1", or receivers will not send them.

Gmail and Yahoo bulk-sender rules

Since February 2024, Gmail and Yahoo have applied requirements that both companies publish. For all senders to Gmail: SPF or DKIM, valid forward and reverse DNS, TLS on the connection, messages formatted to RFC 5322, and a spam rate reported in Google Postmaster Tools below 0.3%. Bulk senders, which Google defines as sending close to 5,000 or more messages to personal Gmail accounts within 24 hours, must also:

  • publish both SPF and DKIM;
  • publish DMARC, at least p=none;
  • align the From domain with the SPF or DKIM domain;
  • offer one-click unsubscribe on marketing and subscribed mail, and honour requests within two days;
  • keep the spam rate below 0.3%, and in practice well under it.

Yahoo's requirements are closely aligned, and Microsoft has announced similar ones for high-volume senders to Outlook.com. Treat these as a minimum, not a target.

Header hygiene

Required and expected headers

From: Example Store <[email protected]>
To: [email protected]
Subject: October product notes
Date: Tue, 20 Oct 2026 09:30:12 +0000
Message-ID: <[email protected]>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
List-Unsubscribe: <https://example.com/unsubscribe/opaque-token-1f9c>,
 <mailto:[email protected]?subject=unsubscribe-1f9c>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
  • Message-ID must be unique per message, with your own domain on the right of the @. Duplicates and IDs naming localhost look like badly written software.
  • Date must be a valid RFC 5322 date with a time-zone offset, generated at send time. Clocks that drift, or dates in the future, count against you.
  • List-Unsubscribe carries an HTTPS URI and optionally a mailto: URI. RFC 8058 adds List-Unsubscribe-Post with exactly the value List-Unsubscribe=One-Click, telling the receiver it may unsubscribe the user with a single POST to the HTTPS URI.

For one-click to be honoured, both headers must be covered by a valid DKIM signature, and the HTTPS endpoint must unsubscribe on a POST alone: no login, no cookies, no confirmation page and no redirect to a form. Put an opaque token in the URI rather than the raw email address. Do not unsubscribe on a GET, because link scanners fetch URLs automatically; a GET may show a page with a button that sends the POST.

Stop leaking infrastructure

Mail written by an application and submitted through an authenticated SMTP session picks up headers that say a lot about your network: a Received line naming the client's address and hostname, X-Originating-IP, X-Mailer with exact software versions, and X-PHP-Originating-Script with a script path. Receivers do not need any of it. In Postfix, give the submission service its own cleanup service that strips them:

# /etc/postfix/master.cf
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o cleanup_service_name=subcleanup
subcleanup unix n       -       y       -       0       cleanup
  -o header_checks=regexp:/etc/postfix/submission_header_checks
# /etc/postfix/submission_header_checks
/^Received:.*with ESMTPSA/     IGNORE
/^X-Originating-IP:/           IGNORE
/^X-Mailer:/                   IGNORE
/^X-PHP-Originating-Script:/   IGNORE

Run postfix reload, then send a test message and read its full headers. For PHP's mail(), also set mail.add_x_header = Off in php.ini. Strip headers before the message is DKIM-signed, and check dkim=pass afterwards. Only do this for mail you originate: never strip Received headers from mail you relay, which receivers use to trace abuse.

DNSSEC

DNSSEC signs your zone so that resolvers can detect forged answers. For email, that protects the MX, SPF, DKIM and DMARC records receivers rely on, and it is a prerequisite for DANE, which lets other servers verify your mail server's TLS certificate through DNS. It is not among the published Gmail or Yahoo requirements; its value is integrity, not placement.

Enabling it

Your DNS host signs the zone; you publish a DS record at your registrar that points to the zone's key-signing key. Choose algorithm 13 (ECDSA P-256 with SHA-256): its keys and signatures are much smaller than RSA's, which keeps responses small. A DS record looks like this:

example.com. IN DS 39507 13 2 D88C254E57F54B863D8939A4CDE3691CDB8F2B526F0F3171E79DFB377EAE64CE

The fields are key tag, algorithm (13), digest type (2, SHA-256) and digest. Copy them from your DNS host exactly. Some registries can pick up keys automatically from CDS and CDNSKEY records. Check the chain with dig +dnssec example.com SOA, looking for the ad flag from a validating resolver, dig DS example.com +short, and delv example.com SOA.

Rollover cautions

A broken chain makes validating resolvers return SERVFAIL for every name in your zone. Mail to you is deferred, and receivers cannot look up your SPF, DKIM or DMARC.

  • Never remove a DNSKEY while a DS at the parent still points to it. Add the new DS first, wait at least the DS record's TTL, then retire the old key.
  • Changing DNS host while signed needs a plan. Either use a multi-signer setup (RFC 8901), or remove the DS at the registrar, wait for the old DS TTL to expire, move, sign with the new host and publish its DS.
  • Algorithm changes need both algorithms published during the transition. Let your DNS host run them.

If you use DANE, a 3 1 1 TLSA record hashes your certificate's public key. Certificate renewals that generate a new key break it unless you publish the new TLSA record before deploying the certificate:

_25._tcp.mail.example.com. IN TLSA 3 1 1 89f98a16ea3239748ca6233f1c84696d1a76424dfd39e0a34536f0ebf4c1abcf

MTA-STS and TLS-RPT

MTA-STS tells other servers that mail to your domain must use TLS with a valid certificate. It protects your inbound mail; it does not change how your outbound mail is placed. Publish a TXT record and a policy file over HTTPS at https://mta-sts.example.com/.well-known/mta-sts.txt:

_mta-sts.example.com. IN TXT "v=STSv1; id=20261020T1200"
version: STSv1
mode: testing
mx: mail.example.com
max_age: 604800

The mta-sts host needs a valid certificate. Start in testing, then switch to enforce and raise max_age (up to 31557600 seconds) once reports are clean. Change the id whenever the policy changes, or senders keep their cached copy. The RFC specifies CRLF line endings for the policy file; many parsers accept LF, but CRLF is the safe choice.

TLS-RPT asks senders to report TLS failures to you as daily JSON:

_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:[email protected]"

BIMI, briefly

BIMI lets supporting mailbox providers show your logo next to authenticated mail. It requires DMARC at enforcement, quarantine or reject, an SVG logo in the SVG Tiny PS profile and, for Gmail, a Verified Mark Certificate or Common Mark Certificate. It is a branding feature, not a deliverability fix.

default._bimi.example.com. IN TXT "v=BIMI1; l=https://example.com/bimi/logo.svg; a=https://example.com/bimi/vmc.pem"

Failure modes and troubleshooting

Read the Authentication-Results header of a message delivered to a test mailbox at each large provider.

SymptomLikely cause
Rejection mentioning a missing PTR recordNo PTR for the sending IP, or the PTR name does not resolve back to it.
spf=permerrorTwo SPF records, or more than 10 lookups.
spf=fail for a legitimate serviceThat service's sending range is not in your record.
dkim=fail, body hash did not verifySomething changed the body after signing, such as a gateway adding a footer.
dkim=neutral or no key foundSelector not published, wrong name, or a TXT string broken by a stray quote.
dmarc=fail although SPF and DKIM passNeither passing domain aligns with From. Use a custom return-path or sign as your domain.
No unsubscribe button in GmailList-Unsubscribe-Post missing, no HTTPS URI, or the headers not DKIM-signed.
Lookups of your domain return SERVFAILBroken DNSSEC chain. Compare the DS at the registrar with the published DNSKEY.

Checklist

  1. PTR for every sending IPv4 and IPv6 address, resolving back to the same address, matching the EHLO name.
  2. One SPF record, under 10 lookups, ending in -all once complete.
  3. 2048-bit DKIM with dated selectors, oversigned From, and a rotation date in the calendar.
  4. DMARC with rua, moving from none to quarantine to reject.
  5. Unique Message-ID, a correct Date, and DKIM-signed List-Unsubscribe and List-Unsubscribe-Post.
  6. Internal Received and X- headers stripped from submitted mail.
  7. DNSSEC with algorithm 13 and a written rollover plan; TLSA updated before key changes.
  8. MTA-STS in testing, then enforce, with TLS-RPT reports read.
  9. Spam rate in Postmaster Tools watched and kept well below 0.3%.

Closing

None of this makes a receiver want your mail. It makes sure that when they decide, they are judging what you send and how recipients respond, not a missing record or a leaky header. Get the plumbing right once, keep the keys rotating, and read the reports.

If you are moving a site and its mail at the same time, the migration page explains how mailboxes and DNS records are copied.