Send Your MIME with Perl
Philip J. Hollenback
Ben Rockwood recently wrote a fairly comprehensive post about sending mime messages from the command line which I enjoyed thoroughly. It's always fun to analyze exactly how things like mime encodings and mail transports work. Well, I think it's fun anyway.
Ben's solution of basically hand-crafting multipart mime messages and feeding them to sendmail is a fine way to go, although I find it a little error-prone and a little cumbersome. What about alternative solutions? You folks who know me have probably already figured out where this is headed: let's do it in perl!
Thus I set off on my favorite pastime, translating all sysadmin tasks into little perl scripts. I probably need therapy.
Perl! What else would you want to use? While there are other tools, perl is the best one based on my sample size of one (I polled myself).
Anyway, once we've picked perl we need to figure out a way to generate and send emails with mime attachments. Let's step back a minute: why not just send straight text in the body and ignore mime? Well, there are a few reasons you might want to consider mime. As Ben notes, it tends to look nicer in modern mail programs. Also, what if you want to send both a text version of your data and an html version? Again, separate mime parts are the way to go.
Perl is great because there are so many modules available on
CPAN. On the other hand, getting things done in perl
is extremely confusing because you have to wade through so many
different modules on CPAN. I usually start by googling
perl blah to
get a sense of which modules to consider. I find that [Stack Overflow
|http://www.stackoverflow.com ] often has the best answers. In this|
- it's well-supported
- it's a 'lite' module and we don't need a lot of features
- I liked the docs
Now my plan was formed: write a perl script using MIME::Lite to send mail. One other requirement is that the script accept it's input on stdin. I sat down and did some coding. Let's look at that. First of course the standards:
#!/usr/bin/perl -w use MIME::Lite;
Next, create a message:
# create a new mime message $msg = MIME::Lite->new( To => "firstname.lastname@example.org", Subject => "I'm a pretty princess", Type => 'multipart/mixed' );
Now we have a message. However, there's no content. Fix that
by adding an attachment with
# add a text attachment $msg->attach( Type => 'TEXT', Data => "some text" );
and of course finally send the message:
# send the message. $msg->send;
I tested this script and it worked just fine. But of course there are a few problems. I can't change the subject or recipient, and I can't pipe anything to the script.
Let's fix the problem of not being able to change the recipient and subject first. Since this script is a relatively quick and dirty hack, I'm not going to bother with getopts or do real option parsing. Just grab the command line arguments directly like this:
$msg = MIME::Lite->new( To => $ARGV, Subject => "I'm a pretty princess", Type => 'multipart/mixed' );
Then you can call the script (let's name it
mmail.pl) like this:
mmail.pl email@example.com "I'm a pretty Princess"
to control who to send the message to anw what the subject should be.
Ok, so now we have a script that sends an email with some text as a mime attachment. However, we're still missing one big thing: piping text from the command line. I want to be able to do something like this:
echo "Brony 4Lif" | mmail.pl firstname.lastname@example.org "I'm a pretty princess"
My first thought was I could slurp the input into a an array and feed that to MIME::Lite:
my @lines = <STDIN>; ... # add stdin as a text attachment $msg->attach( Type => 'TEXT', Data => \@lines )
and indeed that works just fine. But wait, MIME::Lite supports filehandles directly, so let's optimize prematurely:
$msg->attach( Type =>'TEXT', FH => STDIN );
and that's about it. Throw a tiny amount of error checking in the script and here's the finished result:
#!/usr/bin/perl -w use MIME::Lite; $ARGV || die "must supply to"; $ARGV || die "must supply subject"; # create a new mime message $msg = MIME::Lite->new( To => $ARGV, Subject => $ARGV, Type => 'multipart/mixed' ); # add stdin as a text attachment $msg->attach( Type =>'TEXT', FH => STDIN ); # send the message. $msg->send;
Wrap It Up
Now obviously there are many ways to shave this yak. You could use pure shell as Ben did. You could use mpack. You could call mutt on the command line (actually I like that option a lot). None of these are exactly the 'right' answer, that depends on your particular requirements.
I do hope this article demonstrates that it's quite simple to write a small perl script as one solution to this problem. One nice thing about using perl is the flexibility it gives you. It's easy to add other mime parts or extend this script to a more general solution if you want to. Also, using a module like MIME::Light takes care of a lot of annoying details like ensuring your mime parts have the right content-type. Because who wants to deal with that sort of low-level baloney?