Recently at Giant Machines, I was investigating a handful of emails from one of our client projects that were not being properly delivered to specific Internet Service Providers (ISPs). After doing some research on their email settings and configurations, I discovered that an ISP’s spam scanner configuration may have been mistakenly blocking harmless emails for its customers. As a result, I had to troubleshoot and debug these emails locally to identify why they were not being delivered. For the local debugging that I did, I used an npm package called spamscanner, which I will be covering in more depth down below.
55% to 85% of all email traffic is due to spam emails! Due to this, email clients are constantly attempting to block potentially harmful spam emails from reaching their users. However, at times, perfectly valid (and non-spam) emails can be flagged incorrectly.
The process for sending and receiving email has changed quite a bit since its inception, but its basic principles have remained in place. For the purposes of this post, all that you need to know is that web mail clients (such as Gmail, Outlook, etc.) communicate back and forth with dedicated email servers. For a closer look at how email works under the hood, refer to the following article: “How Does Email Work.”
Due to the abundance of email spam, several protocols have been implemented over the years to try to mitigate spam messages by performing various programmatic checks.
The three main protocols are:
- SPF (Sender Policy Framework): Is the sender who they claim to be?
- DKIM (DomainKeys Identified Mail): Encrypts email headers with a private key; servers then use a publicly available key to decrypt the headers and verify the message.
- DMARC (Domain Message Authentication Reporting and Conformance): Built on top of SPF and DKIM; senders can set policies deciding how to handle SPF/DKIM and what to do for failing checks.
For additional information on these email authentication protocols, refer to the following article: “How Email Authentication Works.”
To detect whether incoming emails are malicious, mail servers also use spam scanners, such as Apache’s popular SpamAssasin. The internal workings of these spam scanners can be somewhat complicated (involving Naive Bayes Classifiers on trained, large datasets, for the curious), but the primary takeaway is that these classification systems typically assign a numerical point value to an incoming email to determine the validity of the message. The higher the score, the more likely that the email is spam. For reference, the ISP Optimum states the following regarding their spam filtering:
All incoming emails are evaluated against these spam rules and are assigned a “spam score”. This score determines whether the message will be classified as spam. For standard filtering, the threshold is set at 5, meaning any message with a score of 5 or higher is classified as spam. Messages scoring between 5 and 10 will be delivered, but will include a spam notification in the subject of the email so that you can immediately identify and delete these messages.
Different ISPs have different policy configurations on their chosen spam scanner, but the same idea applies.
As you can see in the screenshot above, the email template that I was investigating received very low scores across the various spam scanners. So what gives? Why were these emails bouncing back, despite having a low score? We will be taking a closer look at this specific issue down below.
Before using a spam scanner to investigate and troubleshoot your email templates, there are some quick wins for lowering your score that can be achieved by following some of the recommendations listed in this article.
Simple Server and Local Email Template
Email clients allow you to download your email messages (with the file extension “.eml”). With these locally saved messages, we can run spamscanner against them to further inspect their contents.
Assuming that you have installed spamscanner and have Node.js locally setup, you may use the following bare-bones script for running the scanner against a locally saved email message:
Note that you can also run the scanner on Strings or Buffers as long as they are a complete SMTP message (i.e., they include headers and the full email contents).
The results of running this script will come back in the following shape:
For a detailed description of these fields, refer to the docs. Typically, the result in the
is_spam field should be enough to give you confidence that your email will not be marked as spam. Note that spamscanner does not assign a numerical value but instead opts to return a boolean.
However, different ISPs use different spam scanners, and it may be necessary to investigate your email messages further. To do so, make sure that the “debug” flag is set to
true, as per the code sample above. You can then inspect the contents of
scanResult.mail, which is an object containing more detailed debugging information regarding the email contents (shown below).
This “.mail” object returns the following shape:
It can be used to get more specific information about the email.
A sample screenshot of the “headers” field that is a part of the “.mail” object is shown below.
In the emails that I was investigating, the spam scanner classifier was marking the email messages as “not spam” but Optimum was appending the following
X-Optimum-spam: yes header to the messages as they were incoming:
This was causing these messages to not only be marked as spam but they were also being blocked/bounced entirely!
If your messages are still being blocked despite a low spam scanner score (or
false if using spamscanner), you may have to take a more manual approach. To do so, I gradually removed parts of the email and re-sent the trimmed-down emails to the ISP that was blocking us. I was eventually able to trace the problem down to this line:
Specifically the mailto: present in the template caused Optimum’s email configuration to flag the email as spam and reject the message outright, despite mailto tags not causing our messages to be flagged as spam by other ISPs.
Additionally, other emails were bouncing back due to the following (modified) copy:
If this email change request wasn’t authorized by you, please click the link below to cancel. If you have any questions, you can contact support via [email protected] or by calling +1 555–555–5555.
+1 present in the template caused Optimum’s spam scanner configuration to flag the email as spam and reject the message outright despite being valid and despite not being flagged by other ISPs or SpamAssasin.
Due to Optimum’s unique SpamAssassin configuration, we were seeing issues for our customers who had an Optimum domain email and attempted to receive emails with “mailto:” or “+1” present. It is not clear why Optimum chooses to block these emails when other ISPs do not, but it could be the case that their configuration is particularly sensitive and errs on the side of caution in attempting to mitigate potential security risks.
The issues that may be affecting your emails may differ but the techniques used here can help you narrow down why your emails may be bouncing back!
- Email servers accept, forward, and deliver messages.
- If an email has not been properly authenticated, the email server must return a failure “bounce” message.
- Spam scanners typically assign a point ranking to emails to classify them as spam or not spam. Hot dog/not hot dog anyone? 🌭
- You can use the npm package spamscanner locally on your email templates to check whether they are being classified as spam.
- When all else fails, you may have to try a more manual approach to debug ISP-specific edge cases.
Have any questions? Comment down below and happy coding!