<?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: Basic Technology</title>
    <description>The latest articles on DEV Community by Basic Technology (@basictechnology).</description>
    <link>https://dev.to/basictechnology</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%2F4028202%2F9a065be5-4455-4516-812b-4618fe0b7800.png</url>
      <title>DEV Community: Basic Technology</title>
      <link>https://dev.to/basictechnology</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/basictechnology"/>
    <language>en</language>
    <item>
      <title>Self-hosting your email? SPF, DKIM and DMARC decide your inbox rate</title>
      <dc:creator>Basic Technology</dc:creator>
      <pubDate>Tue, 14 Jul 2026 07:25:40 +0000</pubDate>
      <link>https://dev.to/basictechnology/self-hosting-your-email-spf-dkim-and-dmarc-decide-your-inbox-rate-4f2l</link>
      <guid>https://dev.to/basictechnology/self-hosting-your-email-spf-dkim-and-dmarc-decide-your-inbox-rate-4f2l</guid>
      <description>&lt;p&gt;I moved outbound email for a couple of side projects off a hosted ESP last year, mostly to stop paying per-seat for what is basically a solved problem. Postfix took an afternoon. Then I sent the first real batch and watched Gmail quietly file most of it under spam and throttle the rest to a trickle. That's when it landed that "I configured SPF" and "Gmail trusts me" are two completely different sentences.&lt;/p&gt;

&lt;p&gt;Deliverability is the part that quietly eats your week. Here's how I think about it now, plus the actual records that get most of your mail into the inbox instead of the spam folder.&lt;/p&gt;

&lt;p&gt;Two things earn you a spot in the inbox, and providers weigh them separately. One is proving the mail is genuinely from you, which is pure crypto and DNS and takes about a weekend. The other is a track record of people wanting your mail, which is behavior, and that takes months. Fail the first and you never get to play for the second. Gmail bins you before anyone reads the subject line.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPF, DKIM, and DMARC each do one job
&lt;/h2&gt;

&lt;p&gt;People list these as one checklist item. They're not interchangeable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SPF&lt;/strong&gt; is a DNS record naming the IPs allowed to send for your domain. One catch trips almost everyone. It checks the &lt;em&gt;envelope&lt;/em&gt; sender, the Return-Path / MAIL FROM your servers negotiate, not the &lt;code&gt;From:&lt;/code&gt; address your recipient actually reads. So on its own it can't stop someone spoofing your domain in the visible From, and it breaks the moment your mail is forwarded and the relaying IP changes. Leave it off entirely, though, and some receivers won't bother evaluating the rest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com.  IN TXT  "v=spf1 mx ip4:203.0.113.10 -all"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DKIM&lt;/strong&gt; signs each message with a private key and publishes the matching public key in DNS at a selector. This is the strong one. It survives ordinary forwarding, which SPF can't, and it proves the signed headers and body weren't altered in transit. There's a caveat every mailing-list operator will remind you of. If a forwarder rewrites what it signed, like a list appending a footer or tagging &lt;code&gt;[list-name]&lt;/code&gt; onto the subject line, the signature breaks too, and that gap is exactly what ARC was built to patch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mail._domainkey.example.com.  IN TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkqhki...QAB"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DMARC&lt;/strong&gt; ties the two together, and its real mechanism is alignment, not a plain pass or fail. It passes if SPF or DKIM passes &lt;em&gt;and&lt;/em&gt; the passing domain lines up with the &lt;code&gt;From:&lt;/code&gt; address the recipient sees. That's why "SPF passed but DMARC failed" is a real and maddening thing: your envelope domain checked out, it just didn't match the visible From. When nothing aligns, DMARC tells the receiver what to do, and if you ask for them, it emails you reports.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Start at &lt;code&gt;p=none&lt;/code&gt;, but include the &lt;code&gt;rua=&lt;/code&gt; address from day one. A bare &lt;code&gt;p=none&lt;/code&gt; sends you nothing, and you'll assume it's broken. Watch the aggregate reports for a week or two, then tighten to &lt;code&gt;quarantine&lt;/code&gt; and eventually &lt;code&gt;reject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The one that cost me an actual day was dumber than any of this. A single wrong character in the DKIM record when I pasted it into DNS. It looked configured. The signature just silently failed to verify, so I'd quietly lost my strongest trust signal and didn't notice until deliverability slipped days later. Now I always confirm the published key matches the private one before I trust it:&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 mail._domainkey.example.com
opendkim-testkey &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-s&lt;/span&gt; mail &lt;span class="nt"&gt;-vvv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;opendkim-testkey&lt;/code&gt; is the one that actually catches the mismatch. &lt;code&gt;dig&lt;/code&gt; only shows you what's published, which looks fine even when it's subtly wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reputation starts cold, so warm the domain and the IP
&lt;/h2&gt;

&lt;p&gt;Even with perfect auth, a brand-new IP has no reputation. And it isn't only the IP: Gmail and Outlook lean heavily on &lt;em&gt;domain&lt;/em&gt; reputation now, and the domain is the part that follows you when you change IPs. So warm both.&lt;/p&gt;

&lt;p&gt;Blast your whole list on day one and you'll get throttled or binned. When I brought that fresh IP up, day one was maybe 50 messages, to my most engaged contacts only. As long as bounces and complaints stayed flat I roughly doubled the next day: 50, then a bit over 100, then a few hundred, backing off the instant either number twitched. A clean few thousand a day took about two weeks. For real volume, tens of thousands, plan on a month or more. The gate is a clean previous step, not the calendar.&lt;/p&gt;

&lt;p&gt;And never send to a bought or scraped list. Spam traps don't care how good your auth is, and that's the fastest way to tank a domain you can't easily recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unglamorous parts beat good copy
&lt;/h2&gt;

&lt;p&gt;Own your Return-Path and actually process bounces, so you stop hammering dead addresses within a send or two instead of a month later. Feedback loops are worth the afternoon they take to set up (Microsoft's JMRP, Yahoo's CFL, and the like), so you hear about spam complaints directly instead of inferring them from a sagging open rate. And keep the list clean. Hard bounces get dropped on the spot, and anyone who hasn't opened in six months gets pruned.&lt;/p&gt;

&lt;p&gt;Content and subject lines matter, but they sit way down this list. Every "why is my mail in spam" fire I've actually chased came back to auth or a dirty list. Almost never the copy.&lt;/p&gt;




&lt;p&gt;This post is adapted from a longer write-up I keep on my own KB, &lt;a href="https://acellesend.com/kb/articles/how-to-set-up-spf-dkim-and-dmarc-records" rel="noopener noreferrer"&gt;the full record-by-record version&lt;/a&gt;, if you want every DNS line and gotcha spelled out. Fwiw, I build AcelleMail, a self-hosted email marketing tool (commercial, not open source, before anyone asks), so I stare at this more than is healthy. None of it is product-specific, though. Postfix, Sendy, or something you rolled yourself: same three records. Get those right first.&lt;/p&gt;

</description>
      <category>selfhosted</category>
      <category>email</category>
      <category>devops</category>
      <category>security</category>
    </item>
  </channel>
</rss>
