Using Email from Within Applications

only for RuBoard - do not distribute or recompile

When I began to outline which applications to discuss in this book, I considered whether to include a guestbook that visitors to your site can sign by filling in a form. A guestbook is simple, so it makes a good introductory form-based application that you can write without a lot of work. Also, you can store the comments in a database, which gives it some relevance to MySQL. But guestbooks have been covered in so many books on Web programming that they ve become standard fodder, and my initial gut reaction to writing about them was ugh.

What to do? I went back and forth from one horn of this dilemma to the other several times. Eventually, I decided to include a guestbook application (right here, in fact), but primarily as an excuse to cover something else: how to add email capabilities to scripts. The guestbook is so simple that we don t have to pay much attention to it, yet it provides a meaningful context for discussing email-generation techniques. Dilemma resolved.

Here is the scenario: You want to provide a guestbook so that people who visit your site can comment on it, and you ll allow people to view comments from previous visitors. But you re concerned about people submitting comments that are unsuitable. If someone writes something that contains foul or libelous language, for instance, you ll want to remove it. That means the guestbook needs to be monitored but you don t want to be bothered having to check it all the time for new entries, or it becomes a burden. One solution to this problem is to have the guestbook itself tell you when it gets a new entry, by sending you an email message containing the content of the entry. That way you receive updates as new entries arrive, just by reading your mail as you normally do. You don t have to keep remembering to visit the guestbook to check for recent additions.

To hold guestbook comments, we ll use the following table. Visitors will provide the name, email, and note columns; we ll provide the id and date values:

 CREATE TABLE guest  (     id      INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,      date    DATE NOT NULL,      name    VARCHAR(50),      email   VARCHAR(60),      note    VARCHAR(255)  ) 

The guest.pl script that handles the guestbook has three functions: display an entry form, process new entries, and show previous entries. Accordingly, the main program looks like this:

 print header (),      start_html (-title => "Sign or View My Guestbook", -bgcolor => "white"),      h2("Sign or View My Guestbook");  my $choice = lc (param ("choice")); # get choice, lowercased  if ($choice eq "")                  # initial script invocation  {     display_entry_form ();  }  elsif ($choice eq "submit")  {     process_form ();  }  elsif ($choice eq "view comments")  {     display_comments ();  }  else  {     print p (escapeHTML ("Logic error, unknown choice: $choice"));  }  print end_html (); 

The display_entry_form() function presents a simple form for entering comments and a button that allows previous comments to be viewed:

 sub display_entry_form  {     print start_form (-action => url ()),          table (             Tr (                 td ("Name:"),                  td (textfield (-name => "name", -size => 60))              ),              Tr (                 td ("Email address:"),                  td (textfield (-name => "name", -size => 60))              ),              Tr (                 td ("Note:"),                  td (textarea (-name => "name", -cols => 60, -rows => 4))              ),          ),          br (),          submit (-name => "choice", -value => "Submit"),          " ",          submit (-name => "choice", -value => "View Comments"),          end_form ();  } 

If the user selects the View Comments button, the script calls display_comments() to look up all the previous comments and display them:

 sub display_comments  { my $dbh;  my $sth;      $dbh = WebDB::connect ();      $sth = $dbh->prepare ("SELECT * FROM guest ORDER BY id");      $sth->execute ();      while (my $ref = $sth->fetchrow_hashref ())      {         print p (escapeHTML ("ID: $ref->{id} Date: $ref->{date}")),                  p (escapeHTML ("Name: $ref->{name}")),                  p (escapeHTML ("Email: $ref->{email}")),                  p (escapeHTML ("Note: $ref->{note}")),                  hr ();      }      $sth->finish ();      $dbh->disconnect ();  } 

If the visitor fills in the form and selects the Submit button, we extract the form contents, add a new record to the database, and send the guestbook administrator an email message:

 sub process_form  { my $dbh;  my $sth;  my $ref;      # Add new entry to database, then reselect it so we can get the      # id and date values to use for the mail message.      $dbh = WebDB::connect ();      $dbh->do ("INSERT INTO guest (date,name,email,note) VALUES (NOW(),?,?,?)",                  undef,                  param ("name"), param ("email"), param ("note"));      $sth = $dbh->prepare ("SELECT * FROM guest WHERE id = LAST_INSERT_ID()");      $sth->execute ();      $ref = $sth->fetchrow_hashref ();      $sth->finish ();      $dbh->disconnect ();      # display confirmation to user      print p ("Thank you.");      # send the entry to the administrator via email      mail_entry ($ref);  } 

In the interest of making the application as simple as possible, process_form() performs no validation whatsoever. It just adds whatever information the user provided, even if there isn t any. Then, after adding the new entry, the application needs to mail a copy of it to you. However, this information must include the ID number of the entry (you ll need that value so you can specify which record to remove if it s found to be objectionable), and we might as well present the date, too. To look up the record just inserted, we identify it using LAST_INSERT_ID(). That function returns the most recent AUTO_INCREMENT value created during the current session with the MySQL server, which just happens to be the id value of the record just inserted. After we have the record, we re ready to send some email by calling mail_entry(). Before describing that function, I ll discuss the general technique for sending mail from a script, because we ll be doing that several places in this book. Then we ll adapt that technique for guest.pl and use it within the context of mailing guestbook entries.

Sending Email from Scripts

To send mail from within a Perl script, it s pretty common on UNIX systems to open a pipe to the sendmail program and write the message into it like so:

 open (OUT, " | /usr/lib/sendmail -t")      or die "Cannot open pipe to sendmail: $!\n";  print OUT "From: $sender\n";  print OUT "To: $recipient\n";  print OUT "Subject: I'm sending you mail\n";  print OUT "\n";     # blank line between headers and message body  print OUT "This is the message body.\n";  close (OUT);    # close pipe to send message 

There are other mail-sending alternatives. The Perl CPAN provides several modules for this purpose; the one we ll use is Mail::Sendmail. One of its advantages is that it doesn t require sendmail and therefore works under both UNIX and Windows.[4]

[4] If Mail::Sendmail isn t available on your system, you should obtain and install it before proceeding. (See Appendix A, Obtaining Software. ) You can use one of the other mail modules in the CPAN if you like, but you ll have to adapt the scripts in this book that assume the use of Mail::Sendmail.

Here is a short script, testmail.pl, that illustrates how to use Mail::Sendmail :

 #! /usr/bin/perl -w  # testmail.pl - Send mail using the Mail::Sendmail module  use strict;  use Mail::Sendmail;  my $recipient = "black-hole\@localhost";    # CHANGE THIS!  my $sender = "black-hole\@localhost";       # CHANGE THIS!  # Set up hash containing mail message information  my %mail = (    From    => $sender,     To      => $recipient,     Subject => "I'm sending you mail",     Message => "This is the message body.\n"  );  sendmail (%mail) or die "sendmail failure sending to $mail{To}: $!\n";  exit (0); 

The script accesses Mail::Sendmail by including the appropriate use statement. Then it sets up a hash containing keys for various parts of the message and passes the hash to the sendmail() function. From, To, Subject, and Message are the most useful attributes, but sendmail() understands others, too. (You should change the $recipient and $sender addresses before you try out this script on your system. However, note that the addresses should include a host name, or sendmail() may reject the message.) For further information, you can read the documentation using this command:

 % perldoc Mail::Sendmail 

Based on the preceding discussion, we can write a mail_entry() function for guest.pl that takes care of sending new entries to the administrator. The argument passed to it is the new entry record from the guest table, supplied as a hash reference. mail_entry() uses the contents of the entry to format a simple message body and mails it out. (You ll want to change the To and From addresses before trying out the script on your own site.)

 sub mail_entry  { my $ref = shift;  my %mail =  (     From => "black-hole\@localhost",            # CHANGE THIS!      To => "black-hole\@localhost",              # CHANGE THIS!      Subject => "Exciting New Guestbook Entry",      Message => "  id:     $ref->{id}  date:   $ref->{date}  name:   $ref->{name}  email:  $ref->{email}  note:   $ref->{note}  "  );      sendmail (%mail);  } 

Removing Guestbook Entries

Eventually, guest.pl may mail an entry to you that you deem unworthy of inclusion in your guestbook. How do you remove it? One way to delete the entry is to use the mysql program and issue the appropriate query manually:

 mysql> DELETE FROM guest WHERE id = 37; 

But it would be more convenient to have a command that requires you to provide only the ID for the entry (or entries) you want to delete:

 %./guest_clobber.pl 37 81 92 

Here s a script you can use for that. There s not much to it, because all it needs to do is construct and run one or more DELETE statements:

 #! /usr/bin/perl -w  # guest_clobber.pl - remove guestbook entries; name IDs on the command line  use strict;  use lib qw(/usr/local/apache/lib/perl);  use WebDB;  die "No arguments given\n" unless @ARGV;  my $dbh = WebDB::connect ();  $dbh->do ("DELETE FROM guest WHERE id = ?", undef, shift (@ARGV)) while @ARGV;  $dbh->disconnect ();  exit (0); 

Dealing with High Guestbook Traffic

If your guestbook turns out to be quite popular, you may find that it s sending you more mail messages than you care to deal with. In that case, you can employ a different approach: Generate a single digest message once per day containing all the entries from the previous day. That means there will be more of a delay before you receive notification about new entries, but you ll get one larger message rather than a bunch of smaller ones. If you want to do this, modify guest.pl by ripping out the code that generates email, and then set up a program that runs on schedule each day to identify the relevant entries and mail them to you.

Job scheduling under UNIX involves adding an entry to your crontab file that specifies a program you want run by the cron program.[5] Details vary slightly among systems; use the following commands to get the particulars for your system about the cron program and about crontab file editing:

[5] Under Windows, you can use one of the cron clones that are available; there are freeware, shareware, and commercial versions. (Appendix A lists where you can obtain these products.)

 % man cron  % man crontab 

On some systems, a separate command describes the crontab file format:

 % man 5 crontab 

To schedule a job, add a line to your crontab file that specifies when the job should run. If I have a script guest_digest.pl installed in my bin directory, I can specify that I want it to run each day at 1:00 a.m. with a crontab line like this:

 0 1 * * * /u/paul/bin/guest_digest.pl 

The first four fields indicate the minute, hour, day, and month the job should run. A * character indicates execution every applicable interval (for example, the * characters in the third and fourth fields mean every day of every month ). The fifth field is used for day-of-week scheduling, with values of 0 through 6 meaning Sunday through Saturday.

Now that we have set up scheduled execution of the guest_digest.pl script, I suppose we d better write it:

 #! /usr/bin/perl -w  # guest_digest.pl - produce digest of yesterday's guestbook entries  use strict;  use lib qw(/usr/local/apache/lib/perl);  use WebDB;  my $prelude = "Yesterday's guestbook entries:";  my $dbh = WebDB::connect ();  my $sth = $dbh->prepare (                 "SELECT * FROM guest                  WHERE date = DATE_SUB(CURRENT_DATE,INTERVAL 1 DAY)                  ORDER BY id");  $sth->execute ();  while (my $ref = $sth->fetchrow_hashref ())  {     # print introductory text, but only before the first      # record, and only if there are any records      if (defined ($prelude))      {         print "$prelude\n";          undef $prelude;      }      print <<EOF;  --------------------------------------------- id:     $ref->{id}  date:   $ref->{date}  name:   $ref->{name}  email:  $ref->{email}  note:   $ref->{note}  EOF  }  $sth->finish ();  $dbh->disconnect ();  exit (0); 

In the crontab entry, I indicated that guest_digest.pl is in the /u/paul/bin directory, so that s where I d install the script. For testing, we can run guest_digest.pl manually at the command line:

 % /u/paul/bin/guest_digest.pl  Yesterday's guestbook entries:  --------------------------------------------- id:     57  date:   2001-01-24  name:   Joe Visitor  email:  joev@joevis.net  note:   Here's my pithy comment!  ... 

The script figures out yesterday s date using MySQL s DATE_SUB() function and displays all the entries created on that date. You ll notice that guest_digest.pl doesn t reference Mail::Sendmail and it doesn t call sendmail(). So, you may be wondering how it sends you a daily mail message when it runs under cron. The answer is that guest_digest.pl itself doesn t, but cron does. When cron runs a program specified in a crontab file, it mails any output produced by the program to the crontab owner. Therefore, we re still generating email we re just not doing so explicitly from the script. If your cron doesn t behave that way and just throws away any job output, you should of course modify guest_digest.pl to mail its output explicitly. In fact, you might decide to do that anyway, because the Subject: header for cron -generated mail isn t especially informative as you ll see when it sends you a message for the first time.

Suggested Modifications

The guestbook application could stand some improvement. The most obvious shortcoming is that process_form() doesn t perform even the most rudimentary form validation, such as making sure the visitor provided at least a name. Add some code to do that.

As written, when you submit a comment, you cannot view comments by other visitors except by returning from the confirmation page to the entry-form page. Improve the application s navigation options by putting a View Comments button in the confirmation page so that visitors can go right to the past comments page from there.

The page generated by display_comments() will become quite large after your guestbook receives a number of entries. Rewrite that function to either limit the display to the n most recent entries, or to present a multiple-page display with each page limited to n entries.

Other Uses for Email

Email can be used in many different ways from your scripts. For example, you can email a message to people who fill in a form. This can be useful if you want to provide additional confirmation to users from whom you ve received a form submission, in a way that s more permanent than displaying a message in a browser window. (Any information in the window disappears as soon as the user closes it or visits another page.) Suppose a customer uses the product-registration application developed earlier in the chapter and happens to fill in the email field in the form. You could use the address to mail a copy of the registration record to that customer. To provide this capability, add this line to the end of the insert_record() function in the prod_reg.pl script:

 mail_confirmation ($field_ref); 

Then add the following function to the script; it checks whether the email address is present and sends a message to that address if so:

 sub mail_confirmation  { my $field_ref = shift;      # reference to field list  my %mail =  (     From => "black-hole\@localhost",        # CHANGE THIS!      Subject => "Your product registration information"  );      # Determine whether or not the email field was filled in      foreach my $f (@{$field_ref})      {         if ($f->{name} eq "email")          {             return unless $f->{value};  # no address; do nothing              $mail{To} = $f->{value};              last;          }      }      $mail{Message} = "Thank you for registering your product.\n";      $mail{Message} .= "This is the information we received:\n\n";      foreach my $f (@{$field_ref})      {         $mail{Message} .= "$f->{label} $f->{value}\n";      }      sendmail (%mail);  } 

Note that the mail_confirmation() function verifies that the email field is filled in before attempting to send mail, but it doesn t check whether the value actually looks like a valid address. We ll see how to perform that kind of test when we write our next application.

Another way to use email within the application would be to have it send you notification if it notices that a user submits a registration with a serial number that duplicates a number already in the table. This probably indicates a typo on some user s part.

Some uses for email apply not at the time you collect information from your visitors, but later. It s fairly common to put a field in a form allowing a user to sign up to receive announcements. You d store the address when the form is submitted, and then retrieve all addresses later each time you have an announcement to send.

only for RuBoard - do not distribute or recompile


MySQL and Perl for the Web
MySQL and Perl for the Web
ISBN: 0735710546
EAN: 2147483647
Year: 2005
Pages: 77
Authors: Paul DuBois

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net