MailTools


 
Network Programming with Perl
By Lincoln  D.  Stein
Slots : 1
Table of Contents
Chapter  7.   SMTP: Sending Mail

    Content

The MailTools module, also written by Graham Barr, is a high-level object-oriented interface to the Internet e-mail system. MailTools, available on CPAN, provides a flexible way to create and manipulate RFC 822-compliant e-mail messages. Once the message is composed , you can send it off using SMTP or use one of several UNIX command-line mailer programs to do the dirty work. This might be necessary on a local network that does not have direct access to an SMTP server.

Using MailTools

A quick example of sending an e-mail from within a script will give you the flavor of the MailTools interface (Figure 7.3).

Figure 7.3. Sending e-mail with Mail::Internet

graphics/07fig03.gif

Lines 1 “2: Load modules We bring in the Mail::Internet module. It brings in other modules that it needs, including Mail::Header, which knows how to format RFC 822 headers, and Mail::Mailer, which knows how to send mail by a variety of methods .

Lines 3 “8: Create header We call Mail::Header->new to create a new header object, which we will use to build the RFC 822 header. After creating the object, we call its add() method several times to add the From:, To:, Cc:, and Subject: lines. Notice that we can add the same header multiple times, as we do with the Cc: line. Mail::Header will also insert other required RFC 822 headers on its own.

Lines 9 “13: Create body We create the body text, which is just a block of text.

Lines 14 “16: Create the Mail::Internet object We now create a new Mail::Internet object by calling the package's new() method. The named arguments include Header , to which we pass the header object that we just created, and Body , which receives the body text. The Body argument expects an array reference containing discrete lines of body text, so we wrap $body into an anonymous array reference. Modify , the third argument to new() , flags Mail::Internet that it is OK to reformat the header lines to meet restrictions on line length that some SMTP mailers impose.

Line 17: Send mail We call the newly created Mail::Internet object's send() method with an argument indicating the sending method to use. The "sendmail" argument indicates that Mail::Internet should try to use the UNIX sendmail program to deliver the mail.

Although at first glance Mail::Internet does not hold much advantage over the Net::SMTP-based mail() subroutine we wrote in the previous section, the ability to examine and manipulate Mail::Header objects gives MailTools its power. Mail::Header is also the base class for MIME::Head, which manipulates MIME-compliant e-mail headers that are too complex to be handled manually.

Mail::Header

E-mail headers are more complex than they might seem at first. Some fields occur just once, others occur multiple times, and some allow multiple values to be strung together by commas or another delimiter . A field may occupy a single line, or may be folded across multiple lines with leading whitespace to indicate the presence of continuation lines. The mail system also places an arbitrary limit on the length of a header line. Because of these considerations, you should be cautious of constructing e-mail headers by hand for anything much more complicated than the simple examples shown earlier.

The Mail::Header module simplifies the task of constructing, examining, and modifying RFC 822 headers. Once constructed , a Mail::Header object can be passed to Internet::Mail for sending.

Mail::Header controls the syntax but not the content of the header, which means that you can construct a header with fields that are not recognized by the mail subsystem. Depending on the mailer, a message with invalid headers might make it through to its destination, or it might get bounced. To avoid this, be careful to limit headers to the fields listed in the SMTP and MIME RFCs (RFC 822 and RFC 2045, respectively). Table 7.2 gives some of the common headers in e-mail messages.

Fields that begin with X- are meant to be used as extensions. You can safely build a header containing any number of X- fields, and the fields will be passed through unmodified by the mail system. For example:

 $header = Mail::Header->new(Modify=>1); $header->add('X-Mailer' => "Fido's mailer v1.0"); $header->add('X-HiMom' => 'Hi mom!'); 

Mail::Header supports a large number of methods. The following list gives the key methods. To create a new object, call the Mail::Header new() method.

Table 7.2. Mail::Header Fields
Bcc Date Received Sender
Cc From References Subject
Comments Keywords Reply-To To
Content-Type Message-ID Resent -From X-*
Content-Transfer-Encoding MIME-Version Resent-To  
Content-Disposition Organization Return- Path  

$head = Mail::Header->new([$arg] [,@options])

The new() method is the constructor for the Mail::Header class. Called with no arguments, it creates a new Mail::Header object containing an empty set of headers.

The first argument, if provided, is used to initialize the object. Two types of arguments are accepted. You may provide an open filehandle, in which case the headers are read from the indicated file, or you may provide an array reference, in which case the headers are read from the array. In either case, each line must be a correctly formatted e-mail header, such as "Subject: this is a subject."

@options , if provided, is a list of named arguments that control various header options. The one used most frequently is Modify , which if set true allows Mail::Header to reformat header lines to make them fully RFC 822-compliant. For example:

 open HEADERS,"./mail.msg"; $head = Mail::Header(\*HEADERS, Modify=>1); 

Once a Mail::Header object is created, you may manipulate its contents in several ways:

$head->read(FILEHANDLE)

As an alternative way to populate a header object, you can create an empty object by calling new() with no arguments, and then read in the headers from a filehandle using read() .

$head->add($ name ,$value [,$index])

$head->replace($name,$value [,$index])

$head->delete($name [,$index])

The add() , replace() , and delete() methods allow you to modify the Mail::Header object. Each takes the name of the field to operate on, the value for the field, and optionally an index that selects a member of a multivalued field.

The add() method appends a field to the header. If $index is provided, it inserts the field into the indicated position; otherwise , it appends the field to the end of the list.

The replace() method replaces the named field with the indicated value. If the field is multivalued, then $index is used to select which value to replace; otherwise, the first field is replaced .

Delete() removes the indicated field.

All three of these methods accept a shortcut form that allows you to specify the field name and value in a single line. This shortcut allows you to replace the Subject line like this:

 $head->replace('Subject: returned to sender') 

rather than like this:

 $head->replace(Subject => 'returned to sender') 

To retrieve information about a header object, you use get() to get the value of a single field, or tags() and commit() to get information about all the available fields.

$line = $head->get($name [,$index])

@lines = $head->get($name)

The get() method retrieves the named field. In a scalar context, it returns the text form of the first indicated field; in a list context it returns all such fields. You may provide an index in order to select a single member of a multivalued field.

A slightly annoying feature of get() is that the retrieved field values contain the terminating newlines. These must be removed manually with chomp() .

@fields = $head->tags

Returns the list of field names (which the Mail::Header documentation calls "tags").

$count = $head->count($tag)

Returns the number of times the given tag appears in the header.

Finally, three methods are useful for exporting the header in various forms:

$string = $head->as_string

Returns the entire header as a string in the form that will appear in the message.

$hashref = $head->header_hashref([\%headers])

The header_hashref() method returns the headers as a hash reference. Each key is the unique name of a field, and each value is an array reference containing the header's contents. This form is suitable for passing to Mail::Mailer->open() , as described later in this chapter.

You may also use this method to set the header by passing it a hash reference of your own devising. The composition of \%headers is similar to header_hashref() 's result, but the hash values can be simple scalars if they are not multivalued.

$head->print([FILEHANDLE])

Prints header to indicated filehandle or, if not specified, to STDOUT . Equivalent to:

 print FILEHANDLE $head->as_string 

Mail::Internet

The Mail::Internet class is a high-level interface to e-mail. It allows you to create messages, manipulate them in various ways, and send them out. It was designed to make it easy to write autoresponders and other mail-processing utilities.

As usual, you create a new object using the Mail::Internet new() method:

$mail = Mail::Internet->new([$arg] [,@options])

The new() method constructs a new Mail::Internet object. Called with no arguments, it creates an empty object, which is ordinarily not particularly useful. Otherwise, it initializes itself from its arguments in much the same way as Mail::Header. The first argument, if provided, may be either a filehandle or an array reference. In the former case, Mail::Internet tries to read the headers and body of the message from the filehandle. If the first argument is an array reference, then the new object initializes itself from the lines of text contained in the array.

@options is a list of named-argument pairs. Several arguments are recognized. Header designates a Mail::Header object to use with the e-mail message. If present, this header is used, ignoring any header information provided in $arg . Similarly, Body points to an array reference containing the lines of the e-mail body. Any body text provided by the $arg input is ignored.

Once the object is created, several methods allow you to examine and modify its contents:

$arrayref = $mail->body

The body() method returns the body of the e-mail message as a reference to an array of lines of text. You may manipulate these lines to modify the body of the message.

$header = $mail->head

The head() method returns the message's Mail::Header object. Modifying this object changes the message header.

$string = $mail->as_string

$string = $mail->as_mbox_string

The as_string() and as_mbox_string() methods both return the message (both header and body) as a single string. The as_mbox_string() function returns the message in a format suitable for appending to UNIX mbox- format mailbox files.

$mail->print([FILEHANDLE})

$mail->print_header([FILEHANDLE})

$mail->print_body([FILEHANDLE})

These three methods print all or part of the message to the designated filehandle or, if not otherwise specified, STDOUT .

Several utility methods perform common transformations on the message's contents:

$mail->add_signature([$file])

$mail->remove_sig([$nlines])

These two methods manipulate the signatures that are often appended to the e-mail messages. The add_signature() function appends the signature contained in $file to the bottom of the e-mail message. If $file is not provided, then the method looks for the file $ENV{HOME}/.signature .

remove_sig() scans the last $nlines of the message body looking for a line consisting of the characters "--", which often sets the body off from the signature. The line and everything below it is removed. If not specified, $nlines defaults to 10.

$reply = $mail->reply

The reply() method creates a new Mail::Internet object with the header initialized to reply to the original message, and the body text indented. This is suitable for autoreply applications.

Finally, the send() method sends the message via the e-mail system:

$result = $mail->send([$method] [,@args])

The send() method converts message into a string and sends it using Mail::Mailer. The $method and @args arguments select and configure the mailing method. The next section describes the available methods ” mail , sendmail , smtp , and test .

If no method is specified, send() chooses a default that should work on your system.

A Mail Autoreply Program

With Mail::Internet, we can easily write a simple autoreply program for received e-mail (Figure 7.4). The autoreply.pl script is similar to the venerable UNIX vacation program. When it receives mail, it checks your home directory for the existence of a file named .vacation . If the file exists, the script replies to the sender using the contents of the file. Otherwise, the program does nothing.

Figure 7.4. An autoreply program

graphics/07fig04.gif

This autoreply script takes advantage of a feature of the UNIX mail system that allows incoming e-mail to be piped to a program. Provided that you're using such a system, you may activate the script by creating a .forward file in your home directory that contains lines like the following:

 lstein  /usr/local/bin/autoreply.pl 

Replace the first line with your login name, and the second with the path to the autoreply script. This tells the mail subsystem to place one copy of the incoming mail in the user -specific inbox, and to send another copy to the standard input of the autoreply.pl script.

Let's step through autoreply.pl .

Lines 1 “3: Load modules We turn on strict type checking and load the Mail::Internet module.

Lines 4 “7: Define constants One problem with working with programs run by the mailer daemon is that the standard user environment isn't necessarily set up. This means that $ENV{HOME} and other standard environment variables may not exist. Our first action, therefore, is to look up the user's home directory and login name and store them in appropriate constants. Lines 4 and 5 use the getpwuid() function to retrieve this information. We then use the HOME constant to find the locations of the .vacation and .signature files.

Lines 8 “9: Create a Mail::Internet object We check that the .vacation file is present and, if it is not, exit. Otherwise, we create a new Mail::Internet object initialized from the message sent us on STDIN .

Lines 10 “19: Check that the message should be replied to We shouldn't autoreply to certain messages, such as those sent to us in the Cc: line, or those distributed to a mailing list. Another type of message we should be very careful not to reply to are bounced messages; replying to those has the potential to set up a nasty infinite loop. The next section of the code tries to catch these situations.

We recover the header by calling the Mail::Internet object's head() method, and perform a series of pattern matches on its fields. First we check that our username is mentioned on the To: line. If not, we may be receiving this message as a Cc: or as a member of a mailing list. We next check the Precedence: field. If it's "bulk," then this message is probably part of a mass mailing. If the Subject: line contains the strings "returned mail" or "bounced mail", or if the sender is the mail system itself (identified variously as "mailer daemon," "mail subsystem," or " postmaster "), then we are likely dealing with returned mail and we shouldn't reply or risk setting up a loop. In each of these cases, we just exit normally.

Lines 20 “21: Generate reply To create a new message initialized as a reply to the original, we call the mail message object's reply() method.

Lines 22 “26: Prepend vacation message to text The reply() method will have created body text consisting of the original message quoted and indented. We prepend the contents of the .vacation file to this. We open the contents of .vacation , call the mail message's body() method to return a reference to the array of body lines, and then use unshift() to insert the contents of .vacation in front of the body. We could replace the body entirely, if we preferred.

Lines 27 “28: Add signature We call the reply's add_signature() method to append the contents of the user's signature file, if any, to the bottom of the message body.

Lines 29 “30: Send message We call the reply's send() method to send the message by the most expedient means.

Here is an example of a reply issued by the autoreply.pl script in response to the sample message we composed with Net::SMTP in the previous section. The text at the top came from ~/.vacation and the signature at the bottom from ~/.signature . The remainder is quoted from the original message.

 To:  John Doe <doe@acme.org> From: L Stein <lstein@lsjs.org> Subject: Re: hello there Date: Fri, 7 Jul 2000 08:12:17 -0400 Message-Id: <200007071212.IAA12128@pesto> Hello, I am on vacation from July 6-July 12, and will not be reading my e-mail. I will respond to this message when I return. Lincoln John Doe <doe@acme.org> writes: > This is just a simple e-mail message. > Nothing to get excited about. > Regards, JD -- ====================================================================== Lincoln D. Stein                  Cold Spring Harbor Laboratory ====================================================================== 

If you adapt this autoreply program to your own use, you might want to check the size of the quoted body and delete it if it is unusually large. Otherwise, you might inadvertently echo back a large binary enclosure.

For complex e-mail-processing applications, you should be sure to check out the procmail program, which uses a special-purpose programming language to parse and manipulate e-mail. A number of sophisticated applications have been written on top of procmail , including autoresponders, mailing list generators, and filters for spam mail.

Mail::Mailer

The last component of MailTools that we consider is Mail::Mailer, which is used internally by Mail::Internet to deliver mail. Mail::Mailer provides yet another interface for sending Internet mail. Although it doesn't provide Mail::Internet's header- and body-handling facilities, I find it simpler and more elegant to use in most circumstances.

Unlike Net::SMTP and Mail::Internet, which use object methods to compose and send mail, the Mail::Mailer object acts like a filehandle. This short code fragment shows the idiom:

 use Mail::Mailer; my $mailer = Mail::Mailer->new; $mailer->open( {To       => 'lstein@lsjs.org',                 From  => 'joe@acme.org',                 CC   => ['jac@acme.org','vvd@acme.org'],                 Subject => 'hello there'}); print $mailer "This is just a simple e-mail message.\n"; print $mailer "Nothing to get excited about.\n\n"; print $mailer "Regards, JD\n"; $mailer->close; 

After creating the object with new() , we initialize it by calling open() with a hash reference containing the contents of the e-mailer header. We then use the mailer object as a filehandle to print several lines of the body text. Then we call the object's close() method to finish processing the message and send it out.

The complete list of Mail::Mailer methods is relatively short.

$mailer = Mail::Mailer->new([$method] [,@args])

The new() method creates a new Mail::Mailer object. The optional $method argument specifies how the mail will be sent out, and @args passes additional arguments to the mailer. Table 7.3 shows the currently recognized mail methods.

The contents of @args depends on the method. In the "mail" and "sendmail" methods, whatever you provide in @args is appended to the command line used to invoke the mail and sendmail programs. For the "smtp" method, you can pass the named argument Server to specify the SMTP server to use. For example:

 $mailer = Mail::Mailer->new('smtp',Server => 'mail.lsjs.org') 

Internally, Mail::Mailer opens up a pipe to the indicated mailer program unless "smtp" is specified, in which case it uses Net::SMTP to send the message. If no method is explicitly provided, then Mail::Mailer scans the command PATH looking for the appropriate executables and chooses the first method it finds, beginning with "mail." The Mail::Mailer documentation describes how you can alter this search order by setting the PERL_MAILERS environment variable.

Table 7.3. Mail:Mailer Mailing Methods
Method Description
mail Use the UNIX mail or mailx programs.
sendmail Use the UNIX sendmail program.
smtp Use Net::SMTP to send the mail.
test A debug mode that prints the contents of the message rather than mailing it.

Once created, you initialize the Mail::Mailer object with a set of header fields:

$fh = $mailer->open(\%headers)

The open() method begins a new mail message with the specified headers. For the "mail", "sendmail", and "test" mailing methods, this call forks and execs the mailer program and then returns a pipe opened on the mailer. For the "smtp" method, open() , returns a tied filehandle that intercepts calls to print() and passes them to the datasend() method of Net::SMTP. The returned filehandle is identical to the original Mail::Mailer object, so you are free to use it as a Boolean indicating success or failure of the open() call.

The argument to open() is a hash reference whose keys are the fields of the mail header, and whose values can be scalars containing the contents of the corresponding field, or array references containing the values for multivalued fields such as Cc: or To:. This format is compatible with the header_hashref() , method of the Mail::Header class. For example:

 $mailer->open({To => ['jdoe@acme.org','coyote@acme.org'],                From => 'lstein@cshl.org'}) or die "can't open: $!"; 

Once the object is initialized, you will print the body of the message to it using it as a filehandle:

 print $mailer "This is the first line of the mail message.\n"; 

When the body is done, you should call the object's close() method:

$mailer->close

close() tidies up and sends the message. You should not use the close() Perl built-in for this purpose, because some of the Mail::Mailer methods need to do postprocessing on the message before sending it.


   
Top


Network Programming with Perl
Network Programming with Perl
ISBN: 0201615711
EAN: 2147483647
Year: 2000
Pages: 173

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