[This is what my lightning talk at Nordic Perl Workshop was supposed to be]

A perfect world would have supported some sort of transactions. Handling email is almost as imperfect as it gets, but sometimes I need to send a mail if and only if some other task succeeds. Looking around CPAN I found dozens of modules for sending mail, but none of them seemed to offer to take real responsibility for sending the mail without trying to send it right ahead.

Making a simple assumption I almost solved my problem: Changing the permissions on existing file should succeed!

The Postfix pickup(8) daemon looks for mail that has been dropped into the maildrop directory. Only files with a the permission bits set to 0744 is feed into the mailsystem. So just write a well formatted file into the maildrop directory and change the permissions when Postfox is permitted the process the mail. This is exactly what Mail::Postfix::Postdrop does. Sending mail with control over the process is simple:


use Mail::Postfix::Postdrop;
my $postdrop = new Mail::Postfix::Postdrop $message;

$postdrop->build;    # Build the content of the queue file
$postdrop->write;    # Write it to the maildrop queue
$postdrop->release;  # Let pickup(8) process the queue file
$postdrop->notify;   # Notify pickup(8) about new files (Optional)

The release method is just changing the permissions on the file i the maildrop directory. This way I can almost make sure that Postfix will take responsibility for delivering the mail but still being able to change my mind. Then we just need to wrap it up into some kind of Transaction object. Let us make something up:


package Transaction::Simple;
use Carp;
sub new {
    my ( $class, %args ) = @_;

    $class = ref $class ? ref $class : $class;

    return bless {
        commit   => $args{commit}   || sub { return 1 },
        rollback => $args{rollback} || sub { return 1 },
        caller   => [ caller() ],
        handled  => 0
    }, $class;
}

sub commit {
    my $self = shift;

    return if $self->{handled};
    $self->{handled} = 1;

    return $self->{commit}->();
}

sub rollback {
    my $self = shift;

    return if $self->{handled};
    $self->{handled} = 1;

    return $self->{rollback}->();
}

sub DESTROY {
    my $self = shift;

    return if $self->{handled};

   carp(sprintf( "Commit object created at %s line %d went unhandled out of scope", $self->{caller}->[1], $self->{caller}->[2] ) );
    return $self->rollback;
}

1;

And the we will send mail with the following function:


sub send {
    my $message = shift;
    my $mail = Mail::Postfix::Postdrop->new($message);
    
    $mail->build();
    $mail->write();

    return Transaction::Simple->new(
        commit => sub { $mail->release(); $mail->notify() },
        rollback => sub { $mail->drop() }
    }
}