Mail::IMAPClient

Description: Recently, I have had the pleasure of getting knee deep into various aspects of Email. One of the things that I consistantly found myself wanting to do was to parse through it. I know the best way to do this is to connect to the IMAP server and download the messages. The best way I have come accross on how to accomplish this task is using Mail::IMAPClient.

CPAN: Mail::IMAPClient

Example 1:
Creating a connection to an IMAP server isn’t complicated. And once you are connected there are many things that you can do to manipulate messages. In this first example, I am merely going to show you how to connect to the mail server and download ALL the messages in specific folders:

# Always be safe
use strict;
use warnings;

# Use the module
use Mail::IMAPClient;

 $imap = Mail::IMAPClient->new( Server  => 'mail.server.com:143',
                                User    => 'me',
                              Password  => 'mypass')
        # module uses eval, so we use $@ instead of $!
        or die "IMAP Failure: $@";

 foreach my $box qw( HAM SPAM ) {
   # Which file are the messages going into
   my $file = "mail/$box";

   # Select the mailbox to get messages from
   $imap->select($box)
        or die "IMAP Select Error: $!";

   # Store each message as an array element
   my @msgs = $imap->search('ALL')
        or die "Couldn't get all messages\n";

   # Loop over the messages and store in file
   foreach my $msg (@msgs) {
     # Pipe msgs through 'formail' so they are stored properly
     open my $pipe, "| formail >> $file"
       or die("Formail Open Pipe Error: $!");

     # Send msg through file pipe
     $imap->message_to_file($pipe, $msg);

     # Close the messgae pipe
     close $pipe
       or die("Formail Close Pipe Error: $!");
   }

   # Close the folder
   $imap->close($box);
 }

 # We're all done with IMAP here
 $imap->logout();

Example 2:
In this next example, we are going to take advantage of some other methods that are provided by this useful little module. We will begin by using the same base as is in Example 1, but we will add some nuances in the middle for functionality.

Note: Messages don’t get immediately deleted with IMAP, only marked for deletion. They aren’t actually deleted until the box is expunged. In this case, it gets done after the looping over each mailbox is complete. This is to say that if the program gets interrupted in the middle that the messages won’t be deleted until the mailbox is officially issued an expunge command.

# Always be safe
use strict;
use warnings;

# Use the module
use Mail::IMAPClient;

 $imap = Mail::IMAPClient->new( Server  => 'mail.server.com:143',
                                User    => 'me',
                              Password  => 'mypass')
        # module uses eval, so we use $@ instead of $!
        or die "IMAP Failure: $@";

 foreach my $box qw( HAM SPAM ) {
   # How many msgs are we going to process
   print "There are ". $imap->message_count($box).
          " messages in the $box folder.\n";

   # Which file are the messages going into
   my $file = "mail/$box";

   # Select the mailbox to get messages from
   $imap->select($box)
        or die "IMAP Select Error: $!";

   # Store each message as an array element
   my @msgs = $imap->search('ALL')
        or die "Couldn't get all messages\n";

   # Loop over the messages and store in file
   foreach my $msg (@msgs) {
     # Pipe msgs through 'formail' so they are stored properly
     open my $pipe, "| formail >> $file"
       or die("Formail Open Pipe Error: $!");

     # Skip the msg if its over 100k
     if ($imap->size($msg) > 100000) {
       $imap->delete_message($msg);
       next;
     }

     # Send msg through file pipe
     $imap->message_to_file($pipe, $msg);

     # Close the messgae pipe
     close $pipe
       or die("Formail Close Pipe Error: $!");

     # Delete each message after downloading
     $imap->delete_message($msg);

     # If we are just testing and want to leave
     #  leave the messages untouched, then we
     #  can use the following line of code
     # $imap->deny_seeing($msg);
   }

   # Expunge and close the folder
   $imap->expunge($box);
   $imap->close($box);
 }

 # We're all done with IMAP here
 $imap->logout();

Sys::Hostname

Description: Sys::Hostname is a relatively small, but very useful module. Just as the module name describes, it gets your system’s hostname. To paraphrase the module’s POD documentation, it will try every conceivable way to get the hostname of the current machine.

CPAN: Sys::Hostname

Example:
The one and only use for this module.

# Always be safe
use strict;
use warnings;

# Use the module
use Sys::Hostname;

# Get the hostname
my $host = Sys::Hostname::hostname();

# The above line can also be written as
#my $host = hostname;

print "You are on: $host\\n";

HTML::Entities

Description: When taking user input through any number of forms, there could be characters that you aren’t expecting. This is exactly what HTML::Entities was designed to handle. When getting the user input, it converts it into a form that can help in mitigating certain types of web based scripting attacks.

CPAN: HTML::Entities

Example 1:
The general example of using

encode_entities()

is probably also the most common. It basically says to encode everything in the string that its possible to encode.

# Always be safe and smart
use strict;
use warnings;

# Use the module
use HTML::Entities;

 my $html = "bad stuff here&#$%";
 $html = encode_entities($html);
 print "HTML: $html\\n";

__OUTPUT__
HTML: bad stuff here&#0

Example 2:
This is the slightly more specific example as it uses only specific sets of characters as the “unsafe” characters.

# Always be safe and smart
use strict;
use warnings;

# Use the module
use HTML::Entities;

 my $html = "bad stuff here&#$%";
 $html = encode_entities($html, "\\x80-\\xff");
 print "HTML: $html\\n";

__OUTPUT__
HTML: bad stuff here&#0

Example 3:
This is an example of

decode_entities()

which does the reverse. It checks the string to see if there are any HTML encoded characters and decodes them into their Unicode equivalent. This is the general version of

decode_entities()

which is similar to the version of

encode_entities()

demonstrated in Example 1.

# Always be safe and smart
use strict;
use warnings;

# Use the module
use HTML::Entities;

 my $html = "encoded: bad stuff here&#0";
 $html = decode_entities($html);
 print "Unicode: $html\\n";

__OUTPUT__
Unicode: encoded: bad stuff here&#0

Mail::Sender

Description: This is probably one of the modules that I use most frequently. I commonly write reporting and statistic generating scripts. When the data is finished being crunched, I then dump it into a scalar and send it off in an email. This is the module that does my dirty work for me.

CPAN: Mail::Sender

Example 1:
This is probably the most common implementation of Mail::Sender that I use. I generally build up what is going to go in the $EMAIL variable throughout the script and then eval it to ensure it gets put into the message.

# Always be safe
use strict;
use warnings;

# Use the module
use Mail::Sender;

# Remove Mail::Sender logo feces from mail header
$Mail::Sender::NO_X_MAILER = 1;

my $EMAIL = "This is the body of the email here.\\n";

  eval {
   # Create the anonymous Mail::Sender object
   (new Mail::Sender)->MailMsg(
   {
     smtp => "mail.mydomain.com",
     from => "\\"Postmaster\\" <postmaster\\@mydomain.com>",
     to   => "\\"Eric Lubow\\" <me\\@mydomain.com.com>",
     cc   => "\\"Tech Support\\" <tech\\@mydomain.com>",
     subject => "Test Email",
     msg  => "$EMAIL"
   })
     # Show the Mail::Sender error since $! isn't set here
     or die "Error Sending Mail: $Mail::Sender::Error";
  };

Example 2:
This example sends a mixed multipart message, with text in HTML and alternative text in plaintext. If the receiving user’s email client can read HTML messages, then the HTML portion of the message will be shown. Otherwise, the plaintext (alternative) will show. The iterative loop sends the email message to as many users as are in the array. The script just provides a qw (quote words) statement to be used in the loop. Your best bet would be to create an array of email addresses and iterate over the array:

 my @emails = ["eric@mydomain.com","techs@mydomain.com"];
 for my $rcpt (@emails) {
   ...
 }

Note: In this example, I have also included a subroutine to create a message boundary. I don’t particularly have anything major against the one’s that are generated by default, I just think they give away a little too much information for my liking. Mine is slightly more random.

# Always be safe
use strict;
use warnings;

# Use the module
use Mail::Sender;

# Remove Mail::Sender logo feces from mail header
$Mail::Sender::NO_X_MAILER = 1;

 for my $rcpt (qw( me@mydomain.com tech@mydomain.com ) ) {
   my $sender = new Mail::Sender
    { smtp => '127.0.0.1',
       from => "$from_name <postmaster\\@mydomain.com>",
       boundary => make_boundary()
    }
    or die "Error in mailing: $Mail::Sender::Error\\n";

   # Begin the message, tells the header what message type
   $sender->OpenMultipart(
     { to        => "eric\\@mydomain.com",
        subject   => "Test Email",
        headers   => "Return-Path: postmaster\\@mydomain.com\\r\\n",
        multipart => "mixed",
     })
     or die "Can't Open message: $sender->{'error_msg'}\n";

     # Change the content-type
     $sender->Part( { ctype => 'multipart/alternative' });

     # Put the plain text into the email (aka the alternative text)
     $sender->Part(
       { ctype       => 'text/plain',
         disposition => 'NONE',
         msg          => "This is a test message.\n\nHere is the plaintext.\\n"
       });

     # Put the HTML text into the HTML (what SHOULD be displayed)
     $sender->Part(
       { ctype       => 'text/html',
         disposition => 'NONE',
         msg         => "This is a <B>TEST</B> message.\\n<br>\\n"
       });

     # The message isn't sent until it's Close()'d
     $sender->Close()
       or die "Failed to send message: $sender->{'error_msg'}\\n";
 }

#######################################################
# Function: Creates a random message boundary
# Parameters: None
# Return: Boundary
# Notes: Bounaries are 43 characters in length
#######################################################
sub make_boundary {
  my ($boundary, $char, $wanted) = ('','',43);

  open R, '<', "/dev/urandom";
  while ($wanted) {
    read (R, $char, 1)
      or die "Couldn't read from /dev/urandom: $!\n";
    if ($char =~ /[A-Za-z0-9]/) {
      $boundary .= $char;
      --$wanted;
    }
  }
  close(R);

  return $boundary;
}