Sending email (in Perl)

Here's the quick code for robustly sending an email using UTF-8, content encoding, mime types, and attachments.

Preamble
use strict; use warnings;

use Date::Format ; use Encode ; use Mail::Sender;

Starting and ending an SMTP transaction
Use mail_sender_open and mail_sender_close to begin and end an SMTP transaction. Note that you can call mail_sender_open repeatedly and it'll reuse the current connection. When you're done, call mail_sender_close explicitly.

sub mail_sender_open { our $mail_sender; unless($mail_sender) { # Do this only once to efficiently pipeline messages to your server. However, # Mail::Sender recommends closing this during delays between batches. $mail_sender = Mail::Sender->new({                        "keepconnection" => 1,                         "on_errors" => "die",                         "smtp" => "smtp.local",                 }); }        return $mail_sender; }

sub mail_sender_close { our $mail_sender; # Cancel a pending message -- one that hasn't called Close. Mail::Sender # automatically Closes a message if you don't explicitly Close or Cancel # it, which is a bad idea. This shouldn't die, but there are cases where a        # latent error can make this complain about not being connected. The eval will # also trap if $mail_sender is undef. eval { $mail_sender->Cancel }; eval { $mail_sender = undef }; # Could die while closing the SMTP connection. return; }

Sending simple messages
This calls mail_sender_open and sends a simple message.

my $body = "Main message body"; eval { # Send a simple message. # Note that we should specify the user's timezone, not the server's.        my $mail_sender = mail_sender_open; local $Mail::Sender::NO_DATE = 1; # Should be an option to $mail_sender->Open. $mail_sender->Open({                "from" => 'from@example.com',                 "fake_from" => 'From Example ',                 "to" => 'to@example.com',                 "fake_to" => 'To Example ',                 "cc" => ["cc1@example.com", "cc2@example.com"],                 "fake_cc" => 'C. C. One , C. C. Two ',                 "bcc" => ["bcc1@example.com", "bcc2@example.com"],                 "subject" => "We like databases.",                 "headers" => Date::Format::time2str("Date: %a, %d %b %Y %H:%M:%S %z (%Z)", time, Time::Zone::tz2zone("CST6CDT", time)),                 "charset" => "utf-8",  # Lower-case seems important here, in spite of the standard. (Apple's mail client?)                 "ctype" => "text/plain",                 "encoding" => "quoted-printable",         }); # The quotes around $body are important. They force Perl to build a temporary # string so that encode doesn't modify $body. Otherwise, on a subsequent # iteration, this will send a blank message. $mail_sender->SendEnc(Encode::encode("utf-8", "${body}", 1)); $mail_sender->Close; }; if($@) { my $err = $@; # Who knows what state Mail::Sender might be in now. This avoids a peculiar bug # that causes Mail::Sender to deadlock on network IO during the next delivery # after a half-baked message. mail_sender_close; die $err; }

Sending messages with attachments
This calls mail_sender_open and sends a message with attachments.

eval { # Send a message with attachments. # Note that we should specify the user's timezone, not the server's.        my $mail_sender = mail_sender_open; local $Mail::Sender::NO_DATE = 1; # Should be an option to $mail_sender->Open. $mail_sender->OpenMultipart({                "from" => 'from@example.com',                 "fake_from" => 'From Example ',                 "to" => 'to@example.com',                 "fake_to" => 'To Example ',                 "cc" => ["cc1@example.com", "cc2@example.com"],                 "fake_cc" => 'C. C. One , C. C. Two ',                 "bcc" => ["bcc1@example.com", "bcc2@example.com"],                 "subject" => "We like databases.",                 "headers" => Date::Format::time2str("Date: %a, %d %b %Y %H:%M:%S %z (%Z)", time, Time::Zone::tz2zone("CST6CDT", time)),         }); $mail_sender->Body({                "charset" => "utf-8",  # Lower-case seems important here, in spite of the standard. (Apple's mail client?)                 "ctype" => "text/plain",                 "encoding" => "quoted-printable",         }); # The quotes around $body are important. They force Perl to build a temporary # string so that encode doesn't modify $body. Otherwise, on a subsequent # iteration, this will send a blank message. $mail_sender->SendEnc(Encode::encode("utf-8", "${body}", 1)); foreach my $attachment (@attachments) { # $attachment contains mime_type, name, and content. if($attachment->{"mime_type"} =~ qr~^text/~is) { $mail_sender->Part({                                "charset" => "utf-8",                                 "ctype" => $attachment->{"mime_type"},                                 "disposition" => "attachment; filename=\"$attachment->{'name'}\";\r\nContent-ID: $attachment->{'name'}",                                 "encoding" => "quoted-printable",                         }); $mail->SendEnc(Encode::encode("utf-8", "$attachment->{'content'}", 1)); } else { $mail_sender->Part({                                "ctype" => $attachment->{"mime_type"},                                 "disposition" => "attachment; filename=\"$attachment->{'name'}\";\r\nContent-ID: $attachment->{'name'}",                                 "encoding" => "base64",                         }); $mail_sender->SendEnc($attachment->{"content"}); }                $mail_sender->EndPart; }        $mail_sender->Close; }; if($@) { my $err = $@; # Who knows what state Mail::Sender might be in now. This avoids a peculiar bug # that causes Mail::Sender to deadlock on network IO during the next delivery # delivery after a half-baked message. mail_sender_close; die $err; }

Cleaning up
Again, remember to close the connection when you're done. This just cleans up; it won't die.

mail_sender_close;