Entries (RSS)  |  Comments (RSS)

Outgoing blacklists, or, stop the bouncing.

At Twitter, we have many users which sign up for the service and mistype or enter invalid email addresses. Our product group doesn’t want us to use email verification, and for the most part, we cannot because we accept signups via mobile (through the 40404 SMS short-code.) If we bounce too many messages for any major provider with a clue (think: hotmail, gmail, yahoo…) they’ll turn us off. If a user signs up for the service with an invalid email service, there’s a fair chance that we could be sending email to a dead address. What do we do?!

There’s two things. First, on outgoing mail, we convert the address to a special VERP address. An email address of “user@example.com” becomes “twitter-welcome-user=example.com@postmaster.twitter.com”. We usually replace “welcome” with something topical so we can track the origin of the message.

Why VERP?

If the mail bounces, the mailer-daemon on the other end will bounce the message back to the postmaster machine, which is a transport method in our inbound Postfix server pool. If an MTA along the transit path destroys the original destination email address, we can recover it from the To: address on the bounce. DJB first devised this for use with qMail, but it works great here.

That server runs a Ruby (originally written in perl) script, that pushes data to an internal API that increments the email address with a bounce score . Get too many points in the bounce score table, and we disable sending to your account. If you change your email address in our database, or ask us to try again, we clear the bounce score. We also clear the bounce score after 30 days or by admin request.

We identify and score bounced emails through the use of a simple regexp based scoring mechanism and/or use the DSN if a Delivery Status Notice is available. A “5″ indicates “hard fail, “2″ = soft fail, and “1″ = we don’t know. Anyone scoring over five is disabled. If it’s the “welcome to twitter” message, we mark it as 10 immediately. Your first mail has to go through, period.

Disabling is accomplished through a reverse blacklist through Postfix’s smtp_recipient_restrictions configuration directive and MySQL via proxymap.

The directives to do this under Postfix 2.5 are pretty simple.

In main.cf, On the central outgoing server(or servers…), Add:

# bounce handling
bouncehandler_destination_recipient_limit = 1
transport_maps = btree:/etc/postfix/transport

smtpd_delay_reject = yes
smtpd_recipient_restrictions = \
  check_recipient_access mysql:/etc/postfix/reject-bouncing.cf, \
        permit_mynetworks, \
        reject_unauth_destination

/etc/postfix/reject-bouncing.cf:

user = your_db_username
password = your_db_password
dbname = your_db_name
hosts = your_db_host
query = SELECT 'DISCARD in_bouncers_table' FROM bouncers WHERE email='%s' AND score >= 5

In master.cf, on the incoming server, add:

bouncehandler  unix  -       n       n       -       -       pipe
  flags=DRhu user=nobody argv=/etc/postfix/bouncehandler.pl

I use the following function to calculate severity of a bounce. It’s not perfect:

sub get_severity {
    my ($es) = @_;
    my $body = $es->body;
    my $subject = $es->header("Subject");
    my $score = 1;

    # DSN Failures
    if ($body =~ /Action: failed/) {
	$score = 5;
    }

    # check through body of message and try to score the bounce.
    # temp failures.
    if ($body =~ /connection refused|host unreachable|host or domain not found/mi) {
	$score = 5;
    } 

    if ($body =~ /quota|mail(box|folder)* is full/mi) {
	# mailbox full, not as bad, we'll try up to 4 times.
	$score = 2;
    } 

    # wierd temporary failures
    if ($body =~ /(junk mail|spam) try again later/mi) {
	$score = 1;
    }

    # temp.
    return $score;
}

I leave it to you to write bouncehandler.pl, including the above code, and the database schema. It’s very simple. Parse the message, create rules, insert/update a record in your database. I used Email::Simple for parsing, and DBI/Mysql for database access.

The DB Schema needs to contain these columns (at a minimum): The email address, a score column, created_at, and updated_at.

My queries look something like this:

# prepare SQL statements for later use
# last times are based on our receipt, not theirs.
$findemail_sth = $dbh->prepare('SELECT id, score FROM bouncers WHERE email=?');
$insert_sth    = $dbh->prepare('INSERT INTO bouncers (email,score,updated_at,user_id) VALUES (?,?,now(),?)');
$update_sth    = $dbh->prepare('UPDATE bouncers SET score=?, updated_at=now(),user_id=? where id=?');

This entry was posted on Friday, December 12th, 2008 at 7:14 pm and is filed under systems administration, twitter. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

  • Bravo !!I have been facing similar issues with my site of sending email to a dead addresses, you have just given an idea to overcome this issue, and thanks for putting up in such an elaborated way
  • bruce
    seems strange to me that your app would continue to send email to the bad email addresses, and then your mail server stops the outgoing send with the reverse blacklist.

    On our system, we just mark the account as disabled, and then the app servers stop sending to it. The mail server is plain old qmail.

    Bounce processing is handled similarly to you, but instead of VERP we just added a extra mail header: "X-playfirst-mail", which contains the email identification (account id, type of email, etc.). A python script picks up the bounce messages, parses out the X-playfirst header, and disables the account.

    - Bruce
  • I have been facing similar issues with my site of sending email to a dead addresses, you have just given an idea to overcome this issue, and thanks for putting up in such an elaborated way..
  • aaronasjones
    Thank you for sharing this information.
    Reverse Access Livedoor
  • pattiefmartin
    Yes, it's cool, and useful for me
    Reverse Access Livedoor
  • Interesting, but this wouldn't work in France, because it is the law to have email confirmation!
  • Yes I was wondering the same, we have to abide by the law and may not be right where confirmations are required..
blog comments powered by Disqus