Sending email (in Perl)
From Devpit
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 Close()s 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 <me@example.com>',
"to" => 'to@example.com',
"fake_to" => 'To Example <to@example.com>',
"cc" => ["cc1@example.com", "cc2@example.com"],
"fake_cc" => 'C. C. One <cc1@example.com>, C. C. Two <cc2@example.com>',
"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 <me@example.com>',
"to" => 'to@example.com',
"fake_to" => 'To Example <to@example.com>',
"cc" => ["cc1@example.com", "cc2@example.com"],
"fake_cc" => 'C. C. One <cc1@example.com>, C. C. Two <cc2@example.com>',
"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();