NAME

Net::SMTP::Pipelining - Send email using ESMTP PIPELINING extension

VERSION

This document describes Net::SMTP::Pipelining version 0.0.4

SYNOPSIS

use Net::SMTP::Pipelining;

my $smtp = Net::SMTP::Pipelining->new("localhost");

my $sender = q(sender@example.com);
my (@successful,@failed);
for my $address (q(s1@example.com), q(s2@example.com), q(s3@example.com)) {
    $smtp->pipeline({ mail => $sender,
                      to   => $address,
                      data => qq(From: $sender\n\nThis is a mail to $address),
                    }) or push @failed,@{$smtp->pipe_rcpts_failed()};
    push @successful, @{$smtp->pipe_rcpts_succeeded()};
}

$smtp->pipe_flush() or push @failed,@{$smtp->pipe_rcpts_failed()};

push @successful, @{$smtp->pipe_rcpts_succeeded()};

print "Sent successfully to the following addresses: @successful\n";
warn "Failed sending to @failed\n" if scalar(@failed) >0;

# More intricate error handling
if (!$smtp->pipeline({ mail => $sender,
                      to   => $address,
                      data => qq(From: $sender\n\nThis is a mail to $address),
                    })) {
    my $errors = $smtp->pipe_errors();

    for my $e (@$errors) {
        print "An error occurred:, we said $e->{command} ";
        print "and the server responded $e->{code} $e->{message}\n"
    }
}

DESCRIPTION

This module implements the client side of the SMTP PIPELINING extension, as specified by RFC 2920 (http://tools.ietf.org/html/rfc2920). It extends the popular Net::SMTP module by subclassing it, you can use Net::SMTP::Pipelining objects as if they were regular Net::SMTP objects.

SMTP PIPELINING increases the efficiency of sending messages over a high-latency network connection by reducing the number of command-response round-trips in client-server communication. To highlight the way regular SMTP differs from PIPELINING (and also the way of working with this module), here is a comparison ($s is the Net::SMTP or Net::SMTP::Pipelining object, $from the sender and $to the recipient):

Regular SMTP using Net::SMTP:

Perl code             Client command        Server response
$s->mail($from);      MAIL FROM: <fr@e.com>
                                            250 Sender <fr@e.com> ok
$s->to($to);          RCPT TO: <to@e.com>
                                            250 Recipient <to@e.com> ok
$s->data();           DATA
                                            354 Start mail,end with CRLF.CRLF
$s->datasend("text"); text
$s->dataend();        .
                                            250 Message accepted

Sending this message requires 4 round-trip exchanges between client and server. In comparison, Pipelined SMTP using Net::SMTP::Pipelining (when sending more than one message) only requires 2 round-trips for the last message and 1 round-trip for the others:

Perl code             Client command        Server response
$s->pipeline(
  { mail => $from,
    to   => $to,
    data => "text",
   });
                      MAIL FROM: <fr@e.com>
                      RCPT TO: <to@e.com>
                      DATA
                                            250 Sender <fr@e.com> ok
                                            250 Recipient <to@e.com> ok
                                            354 Start mail,end with CRLF.CRLF
                      text
                      .
$s->pipeline(
  { mail => $from,
    to   => $to,
    data => "text",
   });
                      MAIL FROM: <fr@e.com>
                      RCPT TO: <to@e.com>
                      DATA
                                            250 Message sent
                                            250 Sender <fr@e.com> ok
                                            250 Recipient <to@e.com> ok
                                            354 Start mail,end with CRLF.CRLF
                      text
                      .
 $s->pipe_flush();
                                            250 Message sent

As you can see, the pipeline call does not complete the sending of a single message. This is because a.) RFC 2920 mandates that DATA be the last command in a pipelined command group and b.) it is at this point uncertain whether another message will be sent afterwards. If another message is sent immediately afterwards, the MAIL, RCPT and DATA commands for this message can be included in the same command group as the text of the previous message, thus saving a round-trip. If you want to handle messages one after the other without mixing them in the same command group, you can call pipe_flush after every call to pipeline, that will work fine but be less efficient (the client-server communication then requires two round-trips per message instead of one).

INTERFACE

CONSTRUCTOR

new ( [ HOST ] [, OPTIONS ] )
$smtp = Net::SMTP::Pipelining->new( @options );

This is inherited from Net::SMTP and takes exactly the same parameters, see the documentation for that module.

METHODS

pipeline ( PARAMETERS )
$smtp->pipeline({ mail => $from, to => $to, data => $text })
    or warn "An error occurred";

Sends a message over the connection. Accepts and requires the following parameters passed in a hash reference:

"mail" - the sender address
"to"   - the recipient address
"data" - the text of the message

Server response messages after the DATA command are checked. On success, pipeline returns true, otherwise false. False is returned if there is any error at all during the sending process, regardless whether a message was sent despite these errors. So for example, sending a message to two recipients, one of which is rejected by the server, will return false, but the message will be sent to the one valid recipient anyway. See below methods for determining what exactly happens during the SMTP transaction.

If the server does not support PIPELINING (according to the initial connection banner), pipeline returns false, throws a warning (s.below DIAGNOSTICS section) and puts the warning message into $smtp->pipe_errors()->[0]{message};

pipe_flush ()
$smtp->pipe_flush() or warn "an error occurred";

Reads the server response, thereby terminating the command group (after a message body has been sent). Will also return false on any error returned by the server, true if no error occurred.

pipe_codes ()
$codes = $smtp->pipe_codes();
print "SMTP codes returned: @$codes\n";

Returns the SMTP codes in server responses to the previous call of pipeline or pipe_flush in an array reference, in the order they were given.

pipe_messages ()
$messages = $smtp->pipe_messages();
print "@$_" for @$messages;

Returns the messages in server responses to the previous call of pipeline or pipe_flush in a reference to an array of arrays. Each server response is an element in the top-level array (in the order they were given), each line in that response is an element in the array beneath that.

pipe_sent ()
$sent = $smtp->pipe_sent();
print "I said: ".join("\n",@$sent)."\n";

Returns the lines Net::SMTP::Pipelining sent to the server in the previous call to pipeline in an array reference, in the order they were sent. The array references in the value returned by pipe_sent will always contain the same number of elements (in the same order) as pipe_codes and pipe_messages, so you can reconstruct the whole SMTP transaction from these.

pipe_errors ()
$err = $smtp->pipe_errors();
for ( @$err ) {
    print "Error! I said $_->{command}, server said: $_->{code} $_->{message}";
}

Returns a reference to an array of hashes which contain information about any exchange that resulted in an error being returned from the server. These errors are in the order they occurred (however, there may of course have been successful commands executed between them) and contain the following information:

command - the command sent by Net::SMTP::Pipelining
code    - the server return code
message - the server return message
pipe_recipients ()
$rcpts = $smtp->pipe_recipients();
print "Successfully sent to @{$rcpts->{succeeded}}\n";
print "Failed sending to @{$rcpts->{failed}}\n";
print "Recipient accepted, send pending: @{$rcpts->{accepted}}\n";

Returns the recipients of messages from the last call to pipeline or pipe_flush. Note that this may include recipients from messages pipelined with a previous call to pipeline, because success of a message delivery can only be reported after the subsequent message is pipelined (or the pipe is flushed). The recipients are returned in a reference to a hash of arrays, the hash has the following keys:

accepted  - Recipient has been accepted after the RCPT TO command, but no
            message has been sent yet.
succeeded - Message to this recipient has been successfully sent
failed    - Message could not be sent to this recipient (either because
            the recipient was rejected after the RCPT TO, or because the
            send of the whole message failed).

Each recipient of all messages sent will appear in either the "succeeded" or "failed" list once, so if all you care about is success you only need to inspect these two.

pipe_rcpts_succeeded ()
$success = $smtp->pipe_rcpts_succeeded();
print "Successfully sent to @success}\n";

Convenience method which returns $smtp->pipe_recipients()->{succeeded}

pipe_rcpts_failed ()
$failed = $smtp->pipe_rcpts_failed();
print "Failed sending to @$failed\n";

Convenience method which returns $smtp->pipe_recipients()->{failed}

DIAGNOSTICS

Last character not sent: %s

Could not send the final <CRLF>.<CRLF> to terminate a message (value of $! is given).

Server does not support PIPELINING, banner was "%s"

The server did not report the PIPELININg extension in it's connection banner, refusing to attempt a send via PIPELINING.

CONFIGURATION AND ENVIRONMENT

Net::SMTP::Pipelining requires no configuration files or environment variables.

DEPENDENCIES

Net::SMTP::Pipelining requires the following modules:

  • version

  • Net::Cmd

  • Net::SMTP

  • IO::Socket

INCOMPATIBILITIES

None reported.

BUGS AND LIMITATIONS

Please report any bugs or feature requests to bug-net-smtp-pipelining@rt.cpan.org, or through the web interface at http://rt.cpan.org.

Caveat: While Net::SMTP::Pipelining is a wonderful piece of software (I'd say that, wouldn't I?), you should consider whether it is the right thing for you. In general, when sending email, you should not have to worry about the details of the send process but rather push it out to a mail server near you as simply as possible. That mail server can then take care of transmitting your email to its destination (possibly using pipelining), and you won't have to worry about it. Of course, if you're writing your own mail server in Perl or have any other reason to want to use this module, you're more than welcome.

ACKNOWLEDGEMENTS

Many thanks to David Cantrell for letting me test on his FreeBSD/OS X boxes, that helped shake out at least one bug. Also thanks to the good folks at http://www.perlmonks.org, especially mr_mischief.

AUTHOR

Marc Beyer <japh@tirwhan.org>

LICENCE AND COPYRIGHT

Copyright (c) 2009-2013, Marc Beyer <japh@tirwhan.org>. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.