Sending Email with Perl Best Practice

Forward

If you are a spammer or otherwise send unsolicited bulk Email, please stop reading this article now. This article has been specially crafted to work poorly for spamming*.

Do everyone on the Internet a favor and find another way of making your living. I firmly believe there is a special section of Hell reserved for your kind.

Intro

One of the best things about programming in Perl is that there is more than one way to do it ( aka TIMTOWTDI ), but the sheer number of available options often confuses new programmers. My advice to aspiring Perl programmers is to take a quick look at the overall landscape of choices you have, ask fellow Perl programmers you respect, pick the one that is the most flexible tool, and use it in all of your code. You should only deviate from your standard approach as a matter of last resort.

There are about 937 different ways to send Email with Perl. Having been an active Perl developer for over 10 years, in the past I've used such techniques as:

  • shelling out to /usr/sbin/sendmail
  • writing my own in-house modules
  • using Net::SMTP directly when the application did not need to send attachments
  • using MIME::Lite when I did need to include attachments

But as anyone who has been involved with Perl for awhile knows, it's a good idea to re-evaluate your current practices to see if a better tool or technique has been developed since last time you researched it.

I recently re-evaluated how I send Email and I've fully switched to using MIME::Lite::TT::HTML, in my opinion this is the best all around Email tool available from CPAN today.

It allows you to:

  • Send your message via the local sendmail or via SMTP.
  • You can craft your message with templates (one for plain text and a separate template for a HTML version). As the name suggests this is done with Template Toolkit templates.
  • It easily handles attachments.
  • It is sufficient from a performance perspective.

If you don't need the ability to have an HTML version of your message, you can simply use MIME::Lite::TT instead. It works in the same way as MIME::Lite::TT::HTML, but only gives you the option of one plain text template per message. The only difference in syntax is instead of passing a hash with two arguments for the Template argument, you pass the filename of the single plain text template.

Why use templates for Email?

You might be asking yourself why would you want to use templates for building your messages. I agree most automated messages, such as those generated by "I forgot my password to your site" links, are small and the benefit of using templates might seem dubious at best.

However, I can guarantee that you will have to, at some point, make a change to the text of the messages, fix a HTML rendering bug, etc. during the production life of your code.

So why hard code it into your application? I see Email as just another View ( in a MVC sense ) of the data and as such, the presentation should be separated from the data. Using templates in this manner also opens up the door to building more reusable components.

Using templates for Email, much like with a website or web application, makes it easy to re-skin an existing application. This could be just to get a new design or look, or possibly to re-purpose an application for something it was never really intended for. If you have thinks 'Thank you from the XYZ.com Company' hard coded all over your application this type of repurposing is difficult at best.

Using MIME::Lite::TT::HTML

Using MIME::Lite::TT::HTML is thankfully very simple. You create an instance of the object and pass it a few parameters, such as To/From addresses, Subject, the templates to use, and the data to use when filling in the templates. Here is a simple example:

#!/usr/bin/perl

 use strict;
 use warnings;
 use MIME::Lite::TT::HTML;

 my %params;

 $params{first_name} = 'Frank';
 $params{last_name}  = 'Wiles';
 $params{amt_due}    = '24.99';

 my %options;
 $options{INCLUDE_PATH} = '/path/to/templates';

 my $msg = MIME::Lite::TT::HTML->new(
            From        =>  'admin@example.com',
            To          =>  'frank@example.com',
            Subject     =>  'Your recent purchase',
            Template    =>  {
                                text    =>  'test.txt.tt',
                                html    =>  'test.html.tt',
                            },
            TmplOptions =>  \%options,
            TmplParams  =>  \%params,
 );

 $msg->send;
  

That's it for the Perl source, here are the two template files I used:

test.txt.tt

Hi [% first_name %],

 This is to confirm your purchase of $ [% amt_due %].

 Thank you!
  

test.html.tt

<html>
 <body>

 <strong>Hi [% first_name %]</strong>,

 <p>
 This is to confirm your purchase of $ [% amt_due %].
 </p>

 <p>
 Thank you!
 </p>
 </body>
 </html>
  

If you notice, neither of the templates used the last name that was provided in the %params hash. This was done to illustrate that you can pass in more data than your templates will actually use. This can be handy because you can pass in all of the related data ( in this case all of the information about the purchase ) during development and then only use the portions you want in production. This way there is no need to constantly adjust your Perl code to match changes in your TT templates.

Sending Email with attachments

Now let's complicate our example above a tiny bit by sending along a PDF receipt of their order. To do this we just add these lines of Perl between the creation of the MIME::Lite::TT::HTML object and the call to send it:

# Set our content type properly
 $msg->attr("content-type"  => "multipart/mixed");

 # Attach a PDF to the message
 $msg->attach(  Type        =>  'application/pdf',
                Path        =>  '/path/to/this-users.pdf',
                Filename    =>  'your-receipt.pdf',
                Disposition =>  'attachment'
 );
 

That is all there is too it. You set the file to be attached's MIME type, the path on disk to where the file is location, the name you would like to give the file, and note that it should be included as an attachment.

Note that you need to ensure the MIME type you are using matches the file to be attached. For example, you shouldn't use 'application/pdf' if you are sending an image in the GIF format, you would set it to 'image/gif' instead. I might also be a good idea to customize the Filename option so the user doesn't end up getting 01234942983423.pdf and not be able to recognize it later. Perhaps something along the lines of 'mystore-receipt-2007-05-29.pdf'.

Sending without Sendmail

If you need to send your message not using a local sendmail installation, but instead via a remote SMTP server you need to adjust the code to be:

$msg->send('smtp', 'smtp.yourisp.com', Timeout => 60 );

Which tells MIME::Lite::TT::HTML to use Net::SMTP in the background to do the actual sending, that it should use smtp.yourisp.com, and have a timeout value of 60 seconds. Everything after the first argument is simply passed on to Net::SMTP.

Other Options

Some other options you might find useful to when working with MIME::Lite::TT::HTML are:

CcCc other addresses
TimeZoneSet the time zone of the sender of the message.
EncodingWhich encoding method to use for the body, the default is to use 7bit encoding. Other options are 8bit, quoted-printable, and base64.
CharsetSet the character set of the message body, for example 'utf8'.

Sometimes it is desirable to create the message, but not send it. Because MIME::Lite::TT and MIME::Lite::TT::HTML use MIME::Lite internally all of the various MIME::Lite methods are also available. So instead of sending the message on directly you can:

$msg->as_stringGenerate the entire message as text as a string
$msg->header_as_stringGenerate just the message header as a string
$msg->body_as_stringGenerate just the body of the message as a string

Conclusion

Hopefully I've shown you the benefits of using templates for your Email sending code, and specifically the benefits of MIME::Lite::TT::HTML. If you have any questions, comments, or corrections please contact me at frank@revsys.com.

Additional Resources

Footnotes

* Actually this is a lie. However, if it keeps a single spammer from learning something new, it was well worth the effort. :)