LanguagesBuilding an effective autoresponder

Building an effective autoresponder

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.



sendmail tutorial One question commonly posed on USENET is: “Where can I find a good autoresponder?” These posts might suggest that autoresponse programs are hopelessly complex and above the grasp of the average programmer. Fortunately, this isn’t the case at all. Autoresponders can be surprisingly simple. With this in mind, the best solution is often to build your own.

First, let’s define what an autoresponder is. Simply put, an autoresponder is a program designed to respond automatically to incoming e-mail messages. Its ultimate purpose is to save time and labor – why spend hours sending the same response to multiple persons, when you can have a mindless “robot” do it for you? An autoresponse is instantaneous in most cases; it never makes typos; and it never calls in sick. If your server’s up, so is the autoresponder.

Replacing process with program

How does the autoresponder work? Normally, e-mail is delivered to a user’s mailbox, but in the case of the autoresponder, it is delivered to a program. The activated program then responds by sending a pre-written message to the original inquirer. Of course, the “canned” message may be updated as often as the programmer desires; this process of updating the response may be an automated cron job in and of itself.

While I’ve used Perl and Sendmail in my examples, you are by no means limited to those two choices. I’ve written autoresponders in Bash and C, and there are a host of other languages that can handle the job. I’ve chosen Perl because of its speed and simplicity, and Sendmail because of its widespread implementation in the Open Source world. If you’re using another e-mail server (qmail, for example) I’m sure you could readily adapt the information contained herein to meet your needs.

Let’s begin by taking a look at what happens when an e-mail message is received by Sendmail. If the recipient is local, Sendmail can do one of four things:

  1. deliver the message to a user’s mailbox,
  2. append the message to a file,
  3. send the message to a list of user addresses, or
  4. hand the message off to a program.

It’s the last option that we’re concerned with.

Local and alias

By default, Sendmail will attempt to deliver e-mail to a user mailbox. To defer an e-mail elsewhere, you’ll need to make a line entry into your aliases file (normally /etc/aliases). (Note: You can find everything you ever wanted to know about your aliases file by typing “man aliases”.)

The aliases file may be opened with any text editor and is comprised of one or more rows, each separated into two columns. The left column is the receiving address side (known as the “local”), and the right column is the intended destination for the e-mail message (known as the “alias”). For example, let’s say you wanted all mail addressed to user “bob” routed instead to user “mary.” You would then have a line such as this in your aliases file:

bob: mary

This tells Sendmail that all mail originally addressed to bob should instead be sent to mary. Note that the “local” address (the left column) ends with a colon. The columns are then separated by one or more white spaces, which can be spaces or tabs.

This syntax is fine for re-routing mail from user to another. However, we want mail to be sent to a program (our autoresponder). To do this, we’ll replace “mary” with the correct syntax for a program:

bob: “|/bin/autoreply”

Now, when a piece of e-mail is sent to “bob”, it’s handed off to the /bin/autoreply program. Note the enclosing quotation marks, as well as the “pipe” character before the program. Without these, Sendmail would assume that /bin/autoreply is a file that the e-mail should be appended to. Naturally, we don’t want that.

You can specify that multiple “locals” (left-side entries) be directed towards the same program, as follows:

sales: “|/bin/autoreply”
info: “|/bin/autoreply”

Or, an even easier method would be:

sales: info
info: “|/bin/autoreply”

Here, “sales” is redirected to “info”, which is then dumped into the /bin/autoreply program. All mail originally addressed to “info” goes straight to the program, without any finagling.

Important: /etc/aliases just contains raw data. The file that Sendmail actually uses is /etc/aliases.db . To create a new /etc/aliases.db (after editing /etc/aliases), you’ll need to run “newaliases”. Don’t forget to do this.




Now that we’ve seen how to get e-mail delivered to a program, it’s time to process the arriving e-mail messages. That’s where Perl comes in.

Answer me!

At its simplest, the autoresponder will read the incoming e-mail, but won’t respond to it. (I realize this technically makes it a government worker, and not an autoresponder.) Here’s how such a program might look:

#!/usr/bin/perl

while (<STDIN>) {

}

exit(0);

Notice that the program receives the e-mail as standard input, just as though it were being fed by a “cat” redirect statement.

From here, we want to pluck out the sender’s return address. Let’s examine the headers of a typical e-mail message, to see how this might best be done:

From rmcginty@earthweb.com Wed Aug 25 15:49:35 1999
Received: from fox.earthweb.com (fox.earthweb.com [206.152.10.2])
by interlink-bbs.com (8.9.3/8.9.3) with ESMTP id PAA18023
for ; Wed, 25 Aug 1999
15:49:31 -0500
Received: from Whisker ([206.152.26.136])
by fox.earthweb.com (8.9.1/8.9.1) with SMTP id QAA11159
for ; Wed, 25 Aug 1999
16:43:57 -0400
From: “Rikki McGinty” <rmcginty@earthweb.com>
To: “Jay Link” <jlink@radish.interlink-bbs.com>
Subject: Autoresponder article
Date: Wed, 25 Aug 1999 16:47:02 -0400
Message-ID: <000f01beef3a$fbb77b00$881a98ce@Whisker.earthweb.com>

Notice that there are two “From” lines; one has a colon, and one does not. It might be tempting to use the coloned “From” line, as it appears to be more straightforward than the other, but it has a problem: it’s inconsistent. The “From with a colon” line can actually use one of four different formats, as shown below:

From: rmcginty@earthweb.com
From: <rmcginty@earthweb.com>
From: “Rikki McGinty” <rmcginty@earthweb.com>
From: rmcginty@earthweb.com (“Rikki McGinty”)

Therefore, I suggest using the initial, colon-less “From” line. You can grab it with a statement like this:

if (/^From /x) { }

To get rid of the word “From” itself, and the following date, use the “split” command:

($from, $address, $date) = split (/ /, $_, 3);

I like consistency; since e-mail addresses can be upper and lower case, I usually convert them all into lower case. This will be useful later, as you’ll see.

$address =~ tr/[A-Z]/[a-z]/;

So now, the program looks like this:

#!/usr/bin/perl

while (<STDIN>) {
if (/^From /x) {
($from, $address, $date) = split (/ /, $_, 3);
$address =~ tr/[A-Z]/[a-z]/;
}
}

exit(0);

Looks good. Unfortunately, we still have a problem.

Smartening code

Some of you may have realized that the body of the incoming e-mail message might contain the word “From ” (followed by a space) at the beginning of a line. If this happens, our address extraction routine will get the proper return address at first pass, but will then replace that address with gibberish later on (i.e., with some unwanted portion of the message). Therefore, we just want the first occurrence of the word “From”, and we want to ignore all future placements.

I use a variable called $flag to accomplish this. When the program starts, $flag is set to 0. When the first “From” line is encountered, $flag becomes one, and all subsequent “From” lines are ignored. Here’s the code:

#!/usr/bin/perl

$flag = 0;

while (<STDIN>) {
if ((/^From /x) && ($flag == 0)) {
($from, $address, $date) = split (/ /, $_, 3);
$address =~ tr/[A-Z]/[a-z]/;
$flag = 1;
}
}

exit(0);

Now we can rest assured that our $address is correct.

The next step is to return the automated reply to the sender. This is, after all, the whole point of the program. See below:

#!/usr/bin/perl

$flag = 0;

while (<STDIN>) {
if ((/^From /x) && ($flag == 0)) {
($from, $address, $date) = split (/ /, $_, 3);
$address =~ tr/[A-Z]/[a-z]/;
$flag = 1;
}
}

open SENDMAIL, “| sendmail -bm -oi -f info@example.com $address”;
print SENDMAIL <<EOM;
From: “Example Bot” <info@example.com>
To: $address
Subject: Your email has been received.

Thanks for writing!
EOM
close SENDMAIL;

exit(0);

And that does it! Of course, you can elaborate as much as you’d like. Remember that there needs to be a blank line after the headers.

Let’s take a moment to go over the command line options I’ve used with Sendmail. The -bm flag invokes Sendmail as a mailer program. -oi means that Sendmail won’t terminate the outgoing message prematurely, should it encounter a line consisting of only a period (“.”). (Normally, most mail programs stop accepting input when they’re given a solitary period. My message didn’t contain any period-only lines, but I use this as a precaution. Should someone absentmindedly change the outgoing message to include a period-only line, the program won’t need to be changed.)

-f means “from”, i.e., the e-mail address from which the autoresponse will appear to have been sent. If this isn’t present, the message will be sent from user “bin” or whatever/whomever invoked the autoresponder. Naturally, -f must be followed by an address. The last argument is the “to” address, which is $address.

Getting loopy

Sometimes, by quirk of fate or deliberate mischief, an autoresponder might get into a perpetual mail loop. For example, if your autoresponder receives a spam from a non-existent address, then it will blindly send a reply to that same address. This will usually generate a “MAILER-DAEMON” error message from the remote mail server, which will be sent (of course) back to the autoresponder. Receiving the error message, your autoresponder will then send your autoresponse to the remote “MAILER-DAEMON” address, which will then result in yet another error. The remote “MAILER-DAEMON” replies, repeat, repeat, Pete fell out and who was left? This can go on forever, and in extreme cases, it’ll bog down your server to such a degree that legitimate mail cannot get through.

My fix is to eliminate all “MAILER-DAEMON” mail. Insert this snippet into the main code:

if ($address =~ /mailer-daemon/) {
exit(0);
} elsif ($address =~ /postmaster/) {
exit(0);
}

We know “mailer-daemon” will be lower-case, because we made $address lower-case with our “tr” line. Remember? Anyway, feel free to include as many “elseif” lines as you deem necessary.

Here’s another “cheater” method: Make the -f “from” address (the one given on the Sendmail invocation line) a false address. If a human is reading your mail, they’ll see the proper “From:” line (From with a colon). If it’s a machine, it’ll reply to the bogus address. If you feel bad about using a false address, you might consider using an alias for “/dev/null” on your machine. All mail sent to /dev/null is deleted, so you could have a line like this in /etc/aliases :

nobody: /dev/null

Then send your mail from “nobody”. Again, a human will just see the proper address. Here’s what the relevant headers would look like:

open SENDMAIL, “| sendmail -bm -oi -f nobody@example.com $address”;
print SENDMAIL <<EOM;
From: “Example Bot” <info@example.com>

See what I mean?

Perhaps you’d like a log of everyone who’s written to your autoresponder? This can be easily accomplished; customize the following as you see fit:

open (FILE, “>>/etc/autorespond.logfile”);
print FILE “$addressn”;
close (FILE);

If you wanted the date as well, you could simply include the first “From” line (from the incoming message) without any editing.

Let’s say you wanted your outgoing message to be contained in an external file (instead of in the program). Just wipe out the “EOM” segments, and everything in between, and replace them with the following file-reading code:

Delete:

print SENDMAIL <<EOM;
<message>
EOM

Add:

open (FILE, “/etc/autorespond.message”);
while (<FILE>) {
print SENDMAIL;
}
close (FILE);

That wraps up most of the options that people commonly use with their autoresponders. It would be trivial to send different messages based upon different criteria: time of day, day of week, originating message’s domain, and so on. It is my hope that the preceding information will give you a base upon which you can expand, explore, and manipulate to make the perfect autoresponder to fit your needs.ø


Related resources

1. Sendmail home page The official home page of Sendmail, maintained by the Sendmail Consortium.
2. Sendmail, by Bryan Costales and Eric Allman. O’Reilly’s classic reference guide.
3. Perl home page Need more information on developing applications with Perl? Here’s the place.
4. IETF’s list of RFCs RFC822 is of particular interest to e-mail application programmers.

Jay Link is twenty-something and lives in Springfield, IL. He administrates InterLink BBS – an unintentionally nonprofit Internet service provider – in his fleeting spare moments as well as working various odd jobs to pay the rent.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories