- You need a gmail account.
- The client from which you send the email (i.e. the client that connects to the SMTP server) must support Simple Authentication and Security Layer (SASL).
Since I use Perl to administer my servers I would like to be able to send alerts from Perl scripts to my gmail account when something happens. (And from my gmail account a copy can be sent to my other email accounts, my cell phone, etc.).
To support SASL you need to install the following modules on top of the standard Perl distribution:
Unfortunately the Net_SSLeay and IO-Socket-SSL contain C code and need to be compiled. Fortunately you might be able to find a Perl repository that has already built these packages for your platform e.g. http://archive.apache.org/dist
rep add "Apache Perl 5.8.x" http://archive.apache.org/dist/perl/win32-bin/ppms/
install Net_SSLeay.pm
install IO-Socket-SSL
install Authen-SASL
With Net-SMTP-SSL you can simply extract the SSL.pm file from the distrbution and place it in the <PERL_INSTALL_DIR>/lib/Net/SMTP directory.
Now that you have all of the packages you need installed, a simple Perl script to send email via the Google SMTP server looks something like:
#!/usr/bin/perl -w
use Net::SMTP::SSL;
sub send_mail {
my $to = $_[0];
my $subject = $_[1];
my $body = $_[2];
my $from = 'johnny@gmail.com';
my $password = 'MySecretGmailPassword';
my $smtp;
if (not $smtp = Net::SMTP::SSL->new('smtp.gmail.com',
Port => 465,
Debug => 1)) {
die "Could not connect to server\n";
}
$smtp->auth($from, $password)
|| die "Authentication failed!\n";
$smtp->mail($from . "\n");
my @recepients = split(/,/, $to);
foreach my $recp (@recepients) {
$smtp->to($recp . "\n");
}
$smtp->data();
$smtp->datasend("From: " . $from . "\n");
$smtp->datasend("To: " . $to . "\n");
$smtp->datasend("Subject: " . $subject . "\n");
$smtp->datasend("\n");
$smtp->datasend($body . "\n");
$smtp->dataend();
$smtp->quit;
}
# Send away!
&send_mail('johnny@mywork.com', 'Server just blew up', 'Some more detail');
Sending email with attachments is a little trickier since we have to construct multi-part messages. The Net::SMTP::Multipart module provides a wrapper around Net::SMTP (but not Net::SMTP::SSL) to support attachments, but I don't like the syntax it requires and lack of MIME types guessing so I extracted the core logic from that module into the example below:
#!/usr/bin/perl -w
use Net::SMTP::SSL;
use MIME::Base64;
use File::Spec;
use LWP::MediaTypes;
sub send_mail_with_attachments {
my $to = shift(@_);
my $subject = shift(@_);
my $body = shift(@_);
my @attachments = @_;
my $from = 'johnny@gmail.com';
my $password = 'MySecretGmailPassword';
my $smtp;
if (not $smtp = Net::SMTP::SSL->new('smtp.gmail.com',
Port => 465,
Debug => 1)) {
die "Could not connect to server\n";
}
# Authenticate
$smtp->auth($from, $password)
|| die "Authentication failed!\n";
# Create arbitrary boundary text used to seperate
# different parts of the message
my ($bi, $bn, @bchrs);
my $boundry = "";
foreach $bn (48..57,65..90,97..122) {
$bchrs[$bi++] = chr($bn);
}
foreach $bn (0..20) {
$boundry .= $bchrs[rand($bi)];
}
# Send the header
$smtp->mail($from . "\n");
my @recepients = split(/,/, $to);
foreach my $recp (@recepients) {
$smtp->to($recp . "\n");
}
$smtp->data();
$smtp->datasend("From: " . $from . "\n");
$smtp->datasend("To: " . $to . "\n");
$smtp->datasend("Subject: " . $subject . "\n");
$smtp->datasend("MIME-Version: 1.0\n");
$smtp->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundry\"\n");
# Send the body
$smtp->datasend("\n--$boundry\n");
$smtp->datasend("Content-Type: text/plain\n");
$smtp->datasend($body . "\n\n");
# Send attachments
foreach my $file (@attachments) {
unless (-f $file) {
die "Unable to find attachment file $file\n";
next;
}
my($bytesread, $buffer, $data, $total);
open(FH, "$file") || die "Failed to open $file\n";
binmode(FH);
while (($bytesread = sysread(FH, $buffer, 1024)) == 1024) {
$total += $bytesread;
$data .= $buffer;
}
if ($bytesread) {
$data .= $buffer;
$total += $bytesread;
}
close FH;
# Get the file name without its directory
my ($volume, $dir, $fileName) = File::Spec->splitpath($file);
# Try and guess the MIME type from the file extension so
# that the email client doesn't have to
my $contentType = guess_media_type($file);
if ($data) {
$smtp->datasend("--$boundry\n");
$smtp->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
$smtp->datasend("Content-Transfer-Encoding: base64\n");
$smtp->datasend("Content-Disposition: attachment; =filename=\"$fileName\"\n\n");
$smtp->datasend(encode_base64($data));
$smtp->datasend("--$boundry\n");
}
}
# Quit
$smtp->datasend("\n--$boundry--\n"); # send boundary end message
$smtp->datasend("\n");
$smtp->dataend();
$smtp->quit;
}
# Send away!
&send_mail_with_attachments('johnny@mywork.com', 'Server just blew up', 'Some more detail', 'C:\logs\server.log', 'C:\logs\server-screenshot.jpg');
A couple of interesting things to note:
- The Google SMTP server is available on port 465, which is the standard port for secure SMTP.
- Google SMTP always uses your fully qualified gmail account name as the "from" (i.e. the account name you used to authenticate with the SMTP server), even if you specify a different from when constructing the message headers and body.