<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kiwitoolkit</title>
    <description>The latest articles on DEV Community by kiwitoolkit (@kiwitoolkit).</description>
    <link>https://dev.to/kiwitoolkit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4210237%2F92c890bb-5dbd-4def-ba15-16f783ecbf60.png</url>
      <title>DEV Community: kiwitoolkit</title>
      <link>https://dev.to/kiwitoolkit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kiwitoolkit"/>
    <language>en</language>
    <item>
      <title>Why your emails land in spam: a practical SPF, DKIM and DMARC checklist</title>
      <dc:creator>kiwitoolkit</dc:creator>
      <pubDate>Tue, 27 Oct 2026 07:54:15 +0000</pubDate>
      <link>https://dev.to/kiwitoolkit/why-your-emails-land-in-spam-a-practical-spf-dkim-and-dmarc-checklist-43p8</link>
      <guid>https://dev.to/kiwitoolkit/why-your-emails-land-in-spam-a-practical-spf-dkim-and-dmarc-checklist-43p8</guid>
      <description>&lt;p&gt;Your app sends a password reset. The user never gets it. Nothing errors on your side: the receiving server simply decided your mail wasn't trustworthy.&lt;/p&gt;

&lt;p&gt;Most of the time the cause is three DNS records: &lt;strong&gt;SPF&lt;/strong&gt;, &lt;strong&gt;DKIM&lt;/strong&gt; and &lt;strong&gt;DMARC&lt;/strong&gt;. Here's what each one does and the mistakes that break them.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPF: who is allowed to send as you
&lt;/h2&gt;

&lt;p&gt;SPF is a TXT record listing the servers allowed to send mail for your domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v=spf1 include:_spf.google.com include:sendgrid.net -all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mistake 1: going over 10 DNS lookups.&lt;/strong&gt; Each &lt;code&gt;include:&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;mx&lt;/code&gt; and &lt;code&gt;redirect&lt;/code&gt; costs at least one lookup, and includes can contain more includes. Go over 10 and SPF returns a &lt;code&gt;permerror&lt;/code&gt;. Many receivers then treat your mail as failing SPF. This happens slowly as you add tools, so nobody notices.&lt;/p&gt;

&lt;p&gt;Fix: remove includes for services you no longer use, and prefer &lt;code&gt;ip4:&lt;/code&gt;/&lt;code&gt;ip6:&lt;/code&gt; entries where a provider gives you fixed IPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 2: two SPF records.&lt;/strong&gt; You can have exactly one &lt;code&gt;v=spf1&lt;/code&gt; TXT record. Merge them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 3: &lt;code&gt;+all&lt;/code&gt;.&lt;/strong&gt; That allows anyone to send as you. Use &lt;code&gt;-all&lt;/code&gt; (fail) or &lt;code&gt;~all&lt;/code&gt; (softfail).&lt;/p&gt;

&lt;h2&gt;
  
  
  DKIM: proving the message wasn't changed
&lt;/h2&gt;

&lt;p&gt;Your sending service signs every message, and the public key lives at &lt;code&gt;selector._domainkey.yourdomain.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake: adding a sending tool and never setting up its DKIM.&lt;/strong&gt; Every service that sends as your domain (CRM, newsletter, helpdesk, transactional email) needs its own DKIM record. Look up its docs for the selector and CNAME/TXT value.&lt;/p&gt;

&lt;p&gt;Also watch for an empty &lt;code&gt;p=&lt;/code&gt; in a DKIM record. That means the key was revoked.&lt;/p&gt;

&lt;h2&gt;
  
  
  DMARC: what to do when checks fail
&lt;/h2&gt;

&lt;p&gt;DMARC ties SPF and DKIM to the visible From: domain and tells receivers what to do with failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_dmarc.yourdomain.com  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mistake: leaving it on &lt;code&gt;p=none&lt;/code&gt; forever.&lt;/strong&gt; &lt;code&gt;none&lt;/code&gt; is monitoring only and blocks nothing. The usual rollout:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;p=none&lt;/code&gt; with &lt;code&gt;rua=&lt;/code&gt; reporting turned on. Read the reports for a few weeks.&lt;/li&gt;
&lt;li&gt;Fix every legitimate sender that fails.&lt;/li&gt;
&lt;li&gt;Move to &lt;code&gt;p=quarantine&lt;/code&gt;, then &lt;code&gt;p=reject&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  While you're there: TLS and headers
&lt;/h2&gt;

&lt;p&gt;Two more cheap checks: make sure your certificate isn't close to expiry (auto-renewal fails more often than you'd think), and that plain HTTP redirects to HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking all of it at once
&lt;/h2&gt;

&lt;p&gt;You can check each record with &lt;code&gt;dig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short TXT yourdomain.com
dig +short TXT _dmarc.yourdomain.com
dig +short TXT google._domainkey.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you'd rather get one page: I built a free &lt;a href="https://kiwitoolkit.com/domain-report" rel="noopener noreferrer"&gt;domain report&lt;/a&gt; that checks SPF (including the lookup count), DKIM on common selectors, DMARC, TLS and security headers in one pass. There's also an &lt;a href="https://kiwitoolkit.com/spf-dkim-dmarc-builder" rel="noopener noreferrer"&gt;SPF/DKIM/DMARC record builder&lt;/a&gt; if you're writing records from scratch. No sign-up, nothing stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Exactly one SPF record, under 10 lookups, ending in &lt;code&gt;-all&lt;/code&gt; or &lt;code&gt;~all&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] DKIM set up for &lt;strong&gt;every&lt;/strong&gt; service that sends as your domain&lt;/li&gt;
&lt;li&gt;[ ] DMARC record exists, with reporting on&lt;/li&gt;
&lt;li&gt;[ ] A plan to move DMARC from &lt;code&gt;none&lt;/code&gt; → &lt;code&gt;quarantine&lt;/code&gt; → &lt;code&gt;reject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] TLS certificate not expiring soon; HTTP redirects to HTTPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What's the strangest deliverability problem you've debugged? Tell me in the comments.&lt;/p&gt;

</description>
      <category>email</category>
      <category>dns</category>
      <category>webdev</category>
      <category>security</category>
    </item>
  </channel>
</rss>
