Web applications that send out emails usually process bounced emails in order to avoid sending emails to the bad addresses in the future. The standard technique for handling bounces is to use a variable envelope return path (VERP).
If you are using Postfix with Ruby on Rails, setting up VERP for outgoing mail is easy. In your environment.rb configuration file, include these settings:
config.action_mailer.delivery_method = :sendmail
config.action_mailer.sendmail_settings = {
:location => '/usr/sbin/sendmail',
:arguments => '-V -f bounces-main -i -t'
}
The -V flag tells Postfix to use VERP. Check the sendmail manpage (man sendmail) to see which flag is necessary on your system. For a default installation of Mac OS X, the flag is -V. For the Postfix installation on our CentOS machines, the flag is -XV. You’ll also need to specify the location of the sendmail binary on your particular system.
Now all emails sent through Rails will include a variable envelope return path. For example, if you send an email to keaka@example.com, it will have a Return-Path like this:
Return-Path: <bounces-main+keaka=example.com@yourdomain.com>
Setting up your system to process incoming delivery failure notifications is very system dependent and can be tricky. Basically, you need to intercept emails sent to the Return-Path address, process each email to determine the original recipient, and then mark the original recipient’s email address as bounced in your database.
We use Postfix virtual aliases, so I added this entry to my /etc/postfix/virtual file:
bounces-main@yourdomain.com bounces@localhost
And then I rebuilt the alias index:
$ sudo postmap /etc/postfix/virtual
I also added a ‘bounces’ alias to my /etc/postfix/aliases file that pipes the email into a Rails ActionMailer model:
bounces: | "RAILS_ENV=production /usr/local/bin/ruby /u/apps/your_app/current/script/runner 'BounceHandler.receive(STDIN.read)'"
And then I ran newaliases to tell Postfix about the changes:
$ sudo newaliases
Your BounceHandler model needs to parse out the original recipient’s address, and then perform some custom business logic to mark the address as bounced in your database. Here’s something similar to what my BounceHandler does (although I log all activity in my real version):
class BounceHandler < ActionMailer::Base
def receive(email)
begin
handle_permanent_failure(email) if (email.body =~ /Status: 5/)
rescue Exception => e
# Rescue all exceptions so that error messages don't get emailed to sender.
# I log the exception.
end
end
private
# Status codes starting with 5 are permanent errors
def handle_permanent_failure(email)
address = original_to(email)
if (address)
email = Email.find_or_create_by_address(address)
email.status = Email::BOUNCED
email.save
end
end
# Returns the email address of the original recipient, or nil.
def original_to(email)
address = nil
# To email address should be in this form:
# bounces-main+foo=example.com@yourdomain.com
match = email.to[0].match(/.*?+(.*)@.*/)
if (match)
address = match[1].gsub(/=/, '@')
end
return(address)
end
end