<?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: Petr Michal</title>
    <description>The latest articles on DEV Community by Petr Michal (@petr_michal_178dc4f87ad91).</description>
    <link>https://dev.to/petr_michal_178dc4f87ad91</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%2F4048945%2Fbe90c809-dd24-48b8-8939-345609d0779f.png</url>
      <title>DEV Community: Petr Michal</title>
      <link>https://dev.to/petr_michal_178dc4f87ad91</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/petr_michal_178dc4f87ad91"/>
    <language>en</language>
    <item>
      <title>HELO vs EHLO: Why Your SMTP Greeting Can Hurt Email Delivery</title>
      <dc:creator>Petr Michal</dc:creator>
      <pubDate>Fri, 07 Aug 2026 07:44:23 +0000</pubDate>
      <link>https://dev.to/petr_michal_178dc4f87ad91/helo-vs-ehlo-why-your-smtp-greeting-can-hurt-email-delivery-3226</link>
      <guid>https://dev.to/petr_michal_178dc4f87ad91/helo-vs-ehlo-why-your-smtp-greeting-can-hurt-email-delivery-3226</guid>
      <description>&lt;h1&gt;
  
  
  HELO vs EHLO: Why Your SMTP Greeting Can Hurt Email Delivery
&lt;/h1&gt;

&lt;p&gt;Your SPF, DKIM, and DMARC records may all look correct while your mail server still introduces itself as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHLO localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHLO server1.internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That greeting happens before the message body is transmitted and can become one of the first identity signals a receiving server evaluates.&lt;/p&gt;

&lt;h2&gt;
  
  
  What HELO and EHLO actually do
&lt;/h2&gt;

&lt;p&gt;When one mail server connects to another, the receiving server first sends an SMTP banner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;220 mx.receiver.example ESMTP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sending server then identifies itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHLO mail.sender.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;HELO&lt;/code&gt; is the older SMTP greeting.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EHLO&lt;/code&gt; is the modern Extended SMTP greeting. It also allows the receiving server to advertise capabilities such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;STARTTLS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SIZE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;8BITMIME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PIPELINING&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DSN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern mail servers normally try &lt;code&gt;EHLO&lt;/code&gt; first and fall back to &lt;code&gt;HELO&lt;/code&gt; only when necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a valid EHLO hostname should look like
&lt;/h2&gt;

&lt;p&gt;A good EHLO identity is normally a publicly resolvable fully qualified domain name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mail.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should not be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost
server1
mail.local
127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A strong configuration creates a consistent identity chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sending IP
    ↓ PTR
mail.example.com
    ↓ A or AAAA
Same sending IP
    ↓ EHLO
mail.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is commonly described as forward-confirmed reverse DNS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PTR and EHLO should agree
&lt;/h2&gt;

&lt;p&gt;Suppose the sending connection comes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;203.0.113.20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its PTR record returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mail.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the SMTP session announces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHLO host123.provider.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server may still deliver mail, but the identity is inconsistent.&lt;/p&gt;

&lt;p&gt;Receiving systems can combine that inconsistency with other signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP reputation&lt;/li&gt;
&lt;li&gt;blocklist status&lt;/li&gt;
&lt;li&gt;SPF, DKIM, and DMARC results&lt;/li&gt;
&lt;li&gt;TLS configuration&lt;/li&gt;
&lt;li&gt;complaint history&lt;/li&gt;
&lt;li&gt;sending patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A HELO mismatch is rarely the only reason a message lands in spam, but it can make an already marginal sender look less trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common HELO and EHLO mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;localhost&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This often happens after installing Postfix, Exim, or another MTA without setting the public hostname.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using an internal hostname
&lt;/h3&gt;

&lt;p&gt;Names such as &lt;code&gt;mail.internal&lt;/code&gt; or &lt;code&gt;server1.lan&lt;/code&gt; cannot be verified through public DNS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing forward DNS
&lt;/h3&gt;

&lt;p&gt;The PTR points to &lt;code&gt;mail.example.com&lt;/code&gt;, but that hostname does not resolve back to the sending IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the wrong identity on a multi-IP server
&lt;/h3&gt;

&lt;p&gt;A server sends from several IP addresses but announces the same hostname for all of them, even though each IP has a different PTR record.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assuming the From domain must match
&lt;/h3&gt;

&lt;p&gt;The EHLO hostname does not necessarily need to equal the visible From domain.&lt;/p&gt;

&lt;p&gt;For example, this can be valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHLO mail.example.net
From: billing@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What matters most at this layer is that the server identity is valid, resolvable, and consistent with the sending infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to check your configuration
&lt;/h2&gt;

&lt;p&gt;Start with the actual outbound IP address, not only the domain used in the From header.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the IP used for outbound SMTP.&lt;/li&gt;
&lt;li&gt;Resolve its PTR record.&lt;/li&gt;
&lt;li&gt;Resolve the returned hostname back to an IP.&lt;/li&gt;
&lt;li&gt;Inspect the EHLO value used by the sending server.&lt;/li&gt;
&lt;li&gt;Verify that the complete identity chain is consistent.&lt;/li&gt;
&lt;li&gt;Check STARTTLS and the server certificate.&lt;/li&gt;
&lt;li&gt;Then verify SPF, DKIM, and DMARC separately.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Example commands
&lt;/h2&gt;

&lt;p&gt;Check reverse DNS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig &lt;span class="nt"&gt;-x&lt;/span&gt; 203.0.113.20 +short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check forward DNS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig mail.example.com A +short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect an SMTP greeting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl s_client &lt;span class="nt"&gt;-starttls&lt;/span&gt; smtp &lt;span class="nt"&gt;-connect&lt;/span&gt; mail.example.com:25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact tests available depend on whether the remote server accepts connections from your network and whether port 25 is blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  HELO is not a replacement for email authentication
&lt;/h2&gt;

&lt;p&gt;A correct EHLO identity does not replace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPF&lt;/li&gt;
&lt;li&gt;DKIM&lt;/li&gt;
&lt;li&gt;DMARC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These mechanisms work at different layers.&lt;/p&gt;

&lt;p&gt;HELO/EHLO and PTR describe the connecting server.&lt;/p&gt;

&lt;p&gt;SPF, DKIM, and DMARC authenticate domains and messages.&lt;/p&gt;

&lt;p&gt;For reliable delivery, both layers should be configured correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the complete guide
&lt;/h2&gt;

&lt;p&gt;The original guide includes more context about HELO, EHLO, DNS identity, and common configuration mistakes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mxfend.com/helo-ehlo-email/" rel="noopener noreferrer"&gt;Read the complete HELO and EHLO guide on MXFend&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use the &lt;a href="https://mxfend.com/smtp-tls-checker/" rel="noopener noreferrer"&gt;MXFend SMTP TLS Checker&lt;/a&gt; to inspect STARTTLS and related mail-server configuration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built MXFend. The diagnostic tools are free to use and do not require an account.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>email</category>
      <category>dns</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Why SPF Breaks After Adding One More Email Provider</title>
      <dc:creator>Petr Michal</dc:creator>
      <pubDate>Mon, 27 Jul 2026 07:43:47 +0000</pubDate>
      <link>https://dev.to/petr_michal_178dc4f87ad91/why-spf-breaks-after-adding-one-more-email-provider-1jle</link>
      <guid>https://dev.to/petr_michal_178dc4f87ad91/why-spf-breaks-after-adding-one-more-email-provider-1jle</guid>
      <description>&lt;h1&gt;
  
  
  Why SPF Breaks After Adding One More Email Provider
&lt;/h1&gt;

&lt;p&gt;Your SPF record may look valid and still produce a &lt;code&gt;PermError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This often happens after connecting one more service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Workspace&lt;/li&gt;
&lt;li&gt;Microsoft 365&lt;/li&gt;
&lt;li&gt;Mailchimp&lt;/li&gt;
&lt;li&gt;Zendesk&lt;/li&gt;
&lt;li&gt;a CRM&lt;/li&gt;
&lt;li&gt;a support platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each &lt;code&gt;include:&lt;/code&gt; can trigger additional DNS lookups. SPF allows no more than &lt;strong&gt;10 DNS-triggering mechanisms&lt;/strong&gt; during one evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple-looking record
&lt;/h2&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:servers.mcsv.net include:mail.zendesk.com -all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The record contains only three &lt;code&gt;include:&lt;/code&gt; mechanisms, but each included record may contain more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;include&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;redirect&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exists&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The limit applies to the &lt;strong&gt;complete evaluation tree&lt;/strong&gt;, not only to the mechanisms visible in the root record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens after the limit is exceeded
&lt;/h2&gt;

&lt;p&gt;The receiving mail server may return an SPF &lt;code&gt;PermError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That does not necessarily mean every message will be rejected. The final outcome depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the receiving provider,&lt;/li&gt;
&lt;li&gt;whether DKIM passes,&lt;/li&gt;
&lt;li&gt;whether SPF or DKIM aligns with DMARC,&lt;/li&gt;
&lt;li&gt;the receiver's local spam-filtering rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the problem can appear inconsistent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one provider accepts the message,&lt;/li&gt;
&lt;li&gt;another sends it to spam,&lt;/li&gt;
&lt;li&gt;another reports an authentication failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to diagnose it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Expand every &lt;code&gt;include:&lt;/code&gt; recursively.&lt;/li&gt;
&lt;li&gt;Count all DNS-triggering mechanisms in the full tree.&lt;/li&gt;
&lt;li&gt;Identify senders and providers that are no longer used.&lt;/li&gt;
&lt;li&gt;Check whether multiple services authorize the same infrastructure.&lt;/li&gt;
&lt;li&gt;Look for nested includes that consume more lookups than expected.&lt;/li&gt;
&lt;li&gt;Avoid blindly flattening records without understanding how the provider updates its IP ranges.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Counting only visible includes
&lt;/h3&gt;

&lt;p&gt;Three visible &lt;code&gt;include:&lt;/code&gt; mechanisms do not necessarily mean three DNS lookups.&lt;/p&gt;

&lt;p&gt;An included record may trigger several additional lookups of its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a second SPF record
&lt;/h3&gt;

&lt;p&gt;A domain should publish a single SPF policy.&lt;/p&gt;

&lt;p&gt;Adding another TXT record beginning with &lt;code&gt;v=spf1&lt;/code&gt; does not extend the existing policy. Multiple SPF records can instead cause a &lt;code&gt;PermError&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flattening and forgetting
&lt;/h3&gt;

&lt;p&gt;Flattening can reduce DNS lookups by replacing includes with IP addresses, but it also creates a maintenance obligation.&lt;/p&gt;

&lt;p&gt;If the provider changes its sending infrastructure and the flattened record is not updated, legitimate mail may stop passing SPF.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safer way to fix it
&lt;/h2&gt;

&lt;p&gt;Start by mapping the complete lookup tree.&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;remove obsolete providers,&lt;/li&gt;
&lt;li&gt;remove duplicate authorization paths,&lt;/li&gt;
&lt;li&gt;simplify unnecessary mechanisms,&lt;/li&gt;
&lt;li&gt;confirm which systems still send mail for the domain,&lt;/li&gt;
&lt;li&gt;flatten only where you understand the update and monitoring strategy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is not only to get below 10 lookups today. The SPF policy should remain correct when providers change their infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the full lookup tree
&lt;/h2&gt;

&lt;p&gt;I built a free SPF lookup visualizer for this type of diagnosis:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mxfend.com/spf-lookup-visualizer/" rel="noopener noreferrer"&gt;Open the MXFend SPF Lookup Visualizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It expands the recursive lookup tree and helps show where the SPF lookup budget is being consumed.&lt;/p&gt;

&lt;p&gt;You can also browse more SPF guides in the&lt;br&gt;
&lt;a href="https://mxfend.com/knowledge/spf/" rel="noopener noreferrer"&gt;MXFend SPF knowledge base&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: I built MXFend. The visualizer is free to use and does not require an account.&lt;/p&gt;
&lt;/blockquote&gt;

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