Tuesday, April 03, 2007

E-mails bouncing back with error "smtp;554 refused mailfrom because of SPF policy"

I have a contact form on my website that allows Internet users to generate an e-mail to a 3rd party user of my site. This results in my server sending e-mail from one domain to another domain, neither of which are my domain. (This is a really common scenario for things like evites, greeting cards, send-to-a-friends, etc).

I've recently been receiving reports e-mails sent in this fashion have been bouncing back with this type of error:

Final-Recipient: rfc822;john.doe@anotherdomain.com
Action: failed
Status: 5.5.0
Diagnostic-Code: smtp;554 refused mailfrom because of SPF policy

Thanks to some research and the Fanatical Support over at rackspace, I learned that "SPF" is a fairly new spam prevention technique (Sender Policy Framework). Basically the receiving server checks to see if the sending servers domain is the same as the "from" address domain. The goal is to prevent users from sending e-mails from "george.w.bush@whitehouse.gov" or whatever.

One workaround is to always have the "From" address be from your domain, but that's really not an option for most of these paradigms (You don't want to receive all the replies to someone else's evite)

Luckily openspf.org has a great page on how Web-Generated Emailers Can Avoid Looking Like Forgers. The solution is to tweak your e-mail headers to show that the sender is your domain, like this:

Return-Path: service@mydomain.com
Sender: service@mydomain.com
From: "Jane Doe" <jane.doe@hotmail.com>
Subject: Jane Doe has sent you an e-mail through my site!

In the above example, the e-mails you generate add the "Return-Path" and "Sender" header values to show that you are being honest about the source of the e-mails. One notable side-effect is that service@mydomain.com will now receive the bounce-backs instead of jane.doe@hotmail.com.

The final trick is to get your code to do this. In .NET, the following C# code function sends an SPF-safe e-mail.
static public bool Send(string to, string subject, string body,
string from, string cc, string bcc) { try { //Create your standard mail message MailMessage mail = new MailMessage(); mail.BodyFormat = MailFormat.Html; mail.To = to; mail.Cc = cc; mail.Bcc = bcc; mail.From = from; mail.Subject = subject; mail.Body = body; //Add the headers to avoid SPF errors. (Make sure to change this to your domain) mail.Headers["Sender"] = "service@mydomain.com"; mail.Headers["Return-Path"] = "service@mydomain.com"; //Send the message SmtpMail.SmtpServer = "localhost"; SmtpMail.Send(mail); } catch (Exception ex) { //Maybe log an error return false; } return true; }

You can check the success of this by viewing the original e-mail headers in the generated e-mails. Some receiving e-mail systems (like Gmail) will even append and SPF status to the header so you can see if you were successful.

The header line in my received e-mail went from this:

Received-SPF: neutral (google.com: 72.123.123.123 is neither permitted nor denied by best guess record for domain of jane.doe@hotmail.com)

To this:

Received-SPF: pass (google.com: best guess record for domain of service@mydomain.com designates 72.123.123.123 as permitted sender)