DEV Community

Cover image for Sherlock Holmes: The Case of the Invalid HeloHost Error
Boopathi
Boopathi

Posted on • Originally published at programmerraja.is-a.dev

Sherlock Holmes: The Case of the Invalid HeloHost Error

This time, our debugging adventure takes us deep into the world of SMTP, where an elusive Invalid HELO error threatened to derail email sending. Let’s dive in!

The Mystery Begins

It was a Friday evening, and I was ready to log off and enjoy the weekend. But just as I was packing up, the customer-facing team reported an issue: a user couldn’t send emails via SMTP from our platform.

Sighing, I pulled out my laptop, checked the logs, and found this cryptic error:

550 Requested action not taken: Invalid HeloHost (#5.5.0)
"responseCode":550, "command":"MAIL FROM"
Enter fullscreen mode Exit fullscreen mode

Not exactly the “Have a great weekend!” message I was hoping for.

The First Hypothesis

This was a new error for me, so I did some quick Googling. My first suspicion? The user might have IP restrictions on their SMTP server.

We asked them to whitelist our server’s IP, hoping that would fix it. But when we retried, the error persisted. Time for a deeper dive.

Digging Deeper

Things rarely work as expected on the first try. So, I turned to Google and ChatGPT for insights. Here’s what I learned:

  • Error Code 550: A permanent failure response.
  • Message Meaning: The recipient’s mail server rejected the MAIL FROM command due to an issue with the HELO/EHLO handshake.
  • Potential Fix: Ensure the sending server uses a fully qualified domain name (FQDN) in the HELO/EHLO command (e.g., EHLO mymailserver.example.com).

Understanding the SMTP Handshake

To get a better grasp of the issue, I reviewed the SMTP handshake process:

SMTP handshake

  1. Client Introduces Itself (EHLO/HELO)
    • The sender’s server initiates the connection.
  2. Server Responds
    • The recipient’s server acknowledges and lists supported features.
  3. Authentication (If Required)
    • If needed, credentials are exchanged.
  4. Email Transaction Begins
    • The sender specifies MAIL FROM and RCPT TO.
    • The email content is sent (DATA command).
  5. Server Confirms & Closes Connection
    • The recipient server acknowledges (250 OK).
    • The sender closes the connection (QUIT command).

More details? Check the official spec here.

The Breakthrough

Armed with this knowledge, I dug into our SMTP implementation. We were using Nodemailer, so I checked how it sends the EHLO command.

Inside the _actionGreeting function, I found this:

this._sendCommand('EHLO ' + this.name);
Enter fullscreen mode Exit fullscreen mode

Then, I traced how this.name is set:

this.name = this.options.name || this._getHostname();
Enter fullscreen mode Exit fullscreen mode

Since we hadn’t explicitly set options.name, it defaulted to _getHostname(), which is defined as:

_getHostname() {
    let defaultHostname;
    try {
        defaultHostname = os.hostname() || '';
    } catch (err) {
        defaultHostname = 'localhost';
    }
    if (!defaultHostname || !defaultHostname.includes('.')) {
        defaultHostname = '[127.0.0.1]';
    }
    if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(defaultHostname)) {
        defaultHostname = `[${defaultHostname}]`;
    }
    return defaultHostname;
}
Enter fullscreen mode Exit fullscreen mode

The Culprit: Private IP in EHLO

Our server was running inside a Kubernetes pod, where _getHostname() returned the pod’s private IP.

According to RFC 5321, SMTP servers may verify the domain name in the EHLO command against the client’s IP, but they must not reject a message solely on a failed verification:

An SMTP server MAY verify that the domain name argument in the EHLO  
command actually corresponds to the IP address of the client.  
However, if the verification fails, the server MUST NOT refuse to  
accept a message on that basis.  
Enter fullscreen mode Exit fullscreen mode

However, for some reason, this particular customer’s server was rejecting the handshake, leading to the Invalid HeloHost error.

The Fix

To resolve the issue, we explicitly set a public hostname for EHLO by passing a valid domain when initializing Nodemailer:

const transporter = nodemailer.createTransport({
    host: "smtp.example.com",
    port: 587,
    secure: false,
    auth: {
        user: "user@example.com",
        pass: "password"
    },
    name: "public.hostname.com"  // <-- Fix: Use a valid hostname
});
Enter fullscreen mode Exit fullscreen mode

With this change, the SMTP connection worked flawlessly, and emails started sending without any issues!

Case Closed

Did it solve the problem? Yes but it also introduced a new one! I'll unravel that mystery in the next post.

Found this helpful? Clap 👏 and follow for more debugging adventures!

Top comments (0)