Python – SPF issue with SpamAssassin

emailpythonspamspamassassinspf

I have an internal management app for a small company, written in Python and running on a Linode VPS, with which users can send occasional emails to their customers. The emails are quite straightforward: a text part (i.e. not HTML) with a PDF invoice attachment, created with the email stdlib module and sent via an external SMTP server (which is not hosted on the VPS).

Everything has been running quite smoothly for a while, but then lately I've been receiving complaints that the emails are often classified as spam by the receiving mail servers. I sent a test email to http://www.mail-tester.com and discovered that adding a few missing headers (Date and Message-ID in particular) helped to reduce the "spaminess" level of my messages. However there is one SPF-related SpamAssassin problem that still eludes me:

SPF_HELO_SOFTFAIL   SPF: HELO does not match SPF record (softfail)

I tried sending the test email to my Gmail account and here are the relevant headers that can be found when I do "Show Original":

Received-SPF: pass (google.com: domain of <sender_address> designates <ip_smtp_server> as permitted sender) client-ip=<ip_smtp_server>;
Received: from [<ip_linode_vps>] (helo=<domain_name_linode_vps>)

From this and some additional informations I gathered here and there, I'm pretty sure that this problem could be fixed by somehow modifying the SPF record of the SMTP server domain (which already exists, as a TXT record with a "v=spf1 a mx... ~all" string containing an ip4 reference to <ip_smtp_server>), but my current understanding is not enough to do so, so I'd appreciate any help.

Update:

<ip_linode_vps> = 69.164.216.89
<domain_name_linode_vps> = li131-89.members.linode.com
<ip_smtp_server> = 192.99.17.51
<domain_name_smtp_server> = mail.roucet.com
<already_existing_spf_record> = "v=spf1 a mx ip4:192.99.17.51 ip4:158.85.89.116 ip4:158.85.77.121 ~all"

Best Answer

All authorized mail servers should be listed in the SPF record. As you now have a new authorized mail server, it should be added. Some SPAM checks differentiate between listed (A, MX) and permitted (~all), and will not treat unlisted address as a pass. This penalizes senders who don't send via an approved server (often spambots). The ~all policy indicates that the user doesn't really care who uses their domain, as compared to the -all policy that could get the email blocked or quarantined.

The alternative approach is to configure the new server to relay messages using the existing server. It is common for applications to allow an email relay server to be configured. In python, you would configure your VPS server name instead of localhost.

Whichever server is sending email, it should add the required headers when receiving the message from your application. Alternatively, you can add the headers in the application. There is a defined format for the date in the Date header. The Message-id header has a defined format, but it not as strict. Message ids resemble an email address, but the left side should be a unique id.

Related Topic