VERP on Rails

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

7 Responses to “VERP on Rails”

  1. renuka says:

    Hi,

    I have done same setting like you have described but still i am not able to get its working still i am not able to get bounce emails so can you say me whats wrong?

    Thanks,

  2. renuka says:

    Hello,

    Above setting is working fine now but now i have one another problem i want to store message id for mail and i am not able to get message id using this sendmail method so can anyone help me?

    Thanks,

  3. rob says:

    I’m a little confused. email is being read in from STDIN, so it’s a string; how does it know what .body and .to mean?

  4. rob says:

    oh, my bad. i didn’t know this:

    “To receive emails, you need to implement a public instance method called receive that takes a tmail object as its single parameter. The Action Mailer framework has a corresponding class method, which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns into the tmail object and calls the receive instance method.”

  5. bounces: | “RAILS_ENV=production /usr/local/bin/ruby /u/apps/your_app/current/script/runner ‘BounceHandler.receive(STDIN.read)’”

    Does this above line start an instance of your rails app for EVERY received bounce? Seems like it could quickly kill your server if you got a bunch of bounces quickly, like while sending out a mass mail. Maybe better to run it from a cron somehow, or am I missing something?

    Thanks for posting this,
    Brian

  6. [...] also read a few articles about having postfix send mail to a script. This one is useful. This article talks about configuring custom reply-to addresses to know which emails bounce, something [...]