Using The Mojo Mail Libraries


Using The Mojo Mail Libraries

This is the nerd chapter.

If you thought the last few chapters were nerdy, well... you haven't see anything yet!

Mojo Mail is relatively civil when working with it's libraries, although version 2.0 has been out since the summer of 2000; which means the code has maintained compatibility with the files it reads and creates since then. I've also used Mojo Mail as a learning tool for myself, so some of the code is written well, other code is a bit long toothed.

Development of Mojo Mail is much like how a city grows and expands. Parts that need attention because they have stopped working or are in need of enhancement get worked on. As program lives go, Mojo Mail, version 2 has beaten all predictions.

In these examples, I'm not going to try to teach you Perl, but you'll get a dose of refreshment. I will teach you what's available for you, using Perl and the Mojo Mail Libraries. I get a huge shiver running down my back when people tell me they learn Perl through the code in Mojo Mail. I'm getting a shiver just thinking that particular thought.


Integrating Mojo Mail Within An Already Existing Program

I could think of a million uses for Mailing List Managing capabilities in other programs. For this example, I'm going to use what is probably the workhorse of the Small Business Website: FormMail. FormMail takes information, usually posted from an HTML form and emails that information to a recipient. Incredibly handy. I'm going to extend this capability and allow it to subscribe an email address, if one gets passed, to a Mojo Mail list.

For this example, I'm going to be using David Cross's version of FormMail.pl, released by the London Perl Mongers. You can grab a copy at:

http://nms-cgi.sourceforge.net/formmail.tar.gz

Pseudo Code

Like a good little programmer, I'm going to think out the problem first, and then make a reusable chunk of code. This isn't really the way I came up with the code, my mind is much too chaotic for that, but I suggest you do this :)

To subscribe an email address to a list, you need an email and a list. You also have to make sure that the email is valid and the list exists. That's the gist of it all. I'm going to add one other variable. On the form that's posted to the FormMail.pl script, I want a field where the would-be form-filler-outer can opt in/out of the subscription. So, let's make some pseudo code, like they do in them fancy schools:

 get email
 get list
 get opt_in
 if: My list exists 
    and my email is valid 
    and opt_in is yes
    subscribe email to list

The Perl Code

Done! Now, all we have to do is translate that into Perl, using the Mojo Mail libraries for help. For sake of clarity, here is what we're making, just because cooking shows on the Food Network do the same thing:

   1: use lib qw(mojo);
   2: use MOJO::List; 
   3: 
   4: sub mojo_mail_subscribe { 
   5:     my $email      = shift;
   6:     my $list       = shift; 
   7:     my $add_email  = shift; 
   8:     
   9:     if((defined($list)) && (defined($email))){
  10:         if(($add_email eq "1") || ($add_email eq "yes")){ 
  11:         my $lh = MOJO::List->new(-List => $list); 
  12:             my ($status, $errors) = $lh->subscription_check(-Email => $email); 
  13:             if($status == 1){ 
  14:                 $lh->add_to_email_list(-Email_Ref => [$email]); 
  15:             }
  16:         }
  17:     }
  18: }

Explanation

I feel just like Randall L Schwartz...

We are now going to pick apart everything, (Line numbers have been added for clarity, take them out if you use this code!) this is pretty compact code, so you're going to learn a lot, I'm thinking.

In Perl, you import a Perl Module (Perl Modules end in .pm, it's all those files in the MOJO directory) you use the use statement:

 use ThisModule;
 use ThatModule;

I'm going to guess that the FormMail.pl script is going to be installed in your cgi-bin. Mojo Mail is also installed in the cgi-bin, but in the mojo directory of the cgi-bin (remember, we made that directory?). So, before we tell our tweaked FormMail.pl script what modules to use, we have to tell them where they are. This will probably do the trick:

   1: use lib qw(mojo);

When the script starts, it will add mojo to its list of directories that it'll look in for Perl Modules. Perl usually has a few directories it looks for by default. For example, the server probably has it's own perl library. So, when I write:

 use CGI;

in a program, Perl's will find it in the server-wide Perl library.

For this example, I only need one Mojo Mail library, MOJO::List, which you can guess, handles stuff about lists.

   2: use MOJO::List;

The rest of the example is written in a subroutine. A subroutine, or function, is a chunk of code that can be used over and over; a little encapsulated thing. In Perl, you write a subroutine like this:

  sub thisthingy { 
  }

basically, sub, the name of the function, left curly brace, the meat and potatoes of your subroutine, right curly brace. I'm naming mine, mojo_mail_subscribe.

 sub mojo_mail_subscribe {

The other cool things about subroutines, is that you can pass arguments to them, to change what they'll do. This makes them more flexible. For this subroutine, I'm going to pass the email I want to subscribe, the list I want it to be subscribed to and a variable holding if they want to be subscribed, or not. When I call this subroutine, it'll look like this:

 mojo_mail_subscribe('someemail@yoursite.com', mylist, 1);

Arguments from a subroutine can be accessed via the @_ array. The magic of Perl allows me to access this array with the shift() function, without an argument. If that last sentence doesn't make sense it will once you play with Perl, welcome to Perldom.

 5:     my $email      = shift;
 6:     my $list       = shift; 
 7:     my $add_email  = shift;

Now that we have these variables set, we can test the subscription:

   9:     if((defined($list)) && (defined($email))){

This line tests if there actually is a list and an email to use. If not, we're not going to try to see if there is a really a list of that name or if the email is valid.

  10:         if(($add_email eq "1") || ($add_email eq "yes")){

Line #10 tests if the we should even try to subscribe the email. In this example, either ``1'' or, ``yes'' will do the trick.

  11:         my $lh = MOJO::List->new(-List => $list);

Line #11 creates a new Mojo::List object, named $lh. Mojo::List is an object oriented Module. To create an object, you have to pass the name of the list you want to work on. Once you have a MOJO::List object, you can call its methods.

   12:             my ($status, $errors) = $lh->subscription_check(-Email => $email);

Speaking of methods, subscription_check is a method of the MOJO::List object. It tests if the email that is passed to it can be subscribed. The following items are checked with this method:

It's a handy little method. It returns a status and a hashref of any errors. If the status is 1, the email can be subscribed. Which leads us to:

  13:             if($status == 1){

And then:

  14:                 $lh->add_to_email_list(-Email_Ref => [$email]);

add_to_email_list is another method of MOJO::List. It takes a reference to an array and subscribes the email addresses inside that array ref into the list.

The last thing we need to do is put a call to this subroutine somewhere in the FormMail.pl script. I'll be nice enough to show you exactly where to put it. Place this code:

 mojo_mail_subscribe($Config{email}, $Form{list}, $Form{add_email});

between these lines in FormMail.pl (around line 219):

 else {
    return_html($date, [@Field_Order]);
 }

like this:

 else {
    mojo_mail_subscribe($Config{email}, $Form{list}, $Form{add_email});
    return_html($date, [@Field_Order]);
 }

The last tweak to the FormMail.pl script is to delete the -T flag from the path to Perl on top of the script, as the Mojo libraries do not yet run in Taint Mode.


Creating a Mojo Mail Plugin - mx Verification

Let's make something relatively useful. One of the banes about managing a mailing list is that email addresses aren't always valid, even though their form is valid. Although Mojo Mail now checks to see if the domain part of an address is valid by doing an mx lookup, it doesn't do this in the list control panel's mass subscription feature. Why? mx lookups can take a little time and if you're trying to subscribe a large amount of addresses, you run the chance of your browser timing out. But, let's make a little plugin that checks an mx lookup on a bunch of email address we give it.

There's a few uses for this function. You could check your existing list, and weed out emails that don't pass the verification, and you could also test addresses that haven't been added yet. It doesn't make sense to make two, completely different scripts for similar functions at the opposite ends of a subscription's life cycle, so let's make this as general use as possible.

A plugin is basically an add on for the list control panel. The screen it creates will blend in with the rest of interface, and a new menu item will be created for it.

The Perl Code

The Code:

   1: #!/usr/bin/perl -w
   2: use strict; 
   3: 
   4: # make sure the MOJO lib is in the lib paths!
   5: use lib qw(./ ./MOJO ../ ../MOJO); 
   6: 
   7: # use some of those Modules
   8: use MOJO::Config;
   9: use MOJO::HTML; 
  10: use MOJO::Guts;
  11: use MOJO::List; 
  12: 
  13: # we need this for cookies things
  14: use CGI; 
  15: my $q = new CGI; 
  16: 
  17: # this is how we find what list we're working on
  18: my %login          = $q->cookie(-name => 'login'); 
  19: 
  20: my $admin_list     = $login{admin_list}               || undef; 
  21: my $admin_password = $login{admin_password}           || undef; 
  22: 
  23: 
  24: # This will take care of all out security woes
  25: my $root_login = check_list_security(-Admin_List      => $admin_list, 
  26:                                      -Admin_Password  => $admin_password, 
  27:                                      -IP_Address      => $ENV{REMOTE_ADDR},
  28:                                      -Function        => 'mx_lookup');
  29: my $list = $admin_list; 
  30: 
  31: # get the list information
  32: my %list_info = open_database(-List => $list); 
  33:                               
  34: # header     
  35: print(admin_html_header(-Title      => "mx lookup Verification",
  36:                         -List       => $list_info{list},
  37:                         -Form       => 0,
  38:                         -Root_Login => $root_login));
  39:     
  40:                    
  41: if(!$q->param('process')){ 
  42: 
  43: 
  44:     
  45:     print $q->p('Warning! mx lookup has not been enabled! '  .
  46:           $q->a({-href=>$MOJO_URL.'?f=list_options'}, 
  47:           $q->b('Enable...'))) 
  48:               unless $list_info{mx_check} ==1 ; 
  49:     
  50:     
  51:     
  52:     print $q->p({-align => 'right'}, 
  53:           $q->start_form(-action => $MOJO_URL, 
  54:                          -method => 'POST',
  55:                          -target => 'text_list'),
  56:         $q->hidden('f', 'text_list'),
  57:         $q->submit(-name  => 'Open Subscription List in New Window',
  58:                    -style => $STYLE{default_submit}) .
  59:         $q->end_form()); 
  60:         
  61:         
  62:         
  63:     
  64:     print $q->p("Paste email addresses that you would like an mx lookup for:") . 
  65:           $q->start_form()                                                     . 
  66:           $q->p(
  67:           $q->textarea(-name => 'addresses',
  68:                        -rows  => 5,
  69:                        -cols  => 40,
  70:                                 ))                                             . 
  71:           $q->hidden('process', 'true')                                        .
  72:           $q->submit(-name => 'Verify...',
  73:                     -style => $STYLE{green_submit})                                                 .
  74:           $q->end_form(); 
  75: 
  76: 
  77: 
  78:     }else{ 
  79:         
  80:         my $lh = MOJO::List->new(-List => $list); 
  81:         my @emails = split(/\s+|,|;|\n+/, $q->param('addresses'));
  82:         my @passed;
  83:         my @failed; 
  84:         
  85:         ###################################################################
  86:         #
  87:         #
  88:         
  89:         foreach my $email(@emails){ 
  90:             my ($status, $errors) = $lh->subscription_check(-Email => $email); 
  91:             if($errors->{mx_lookup_failed} == 1){
  92:                 push(@failed, $email);    
  93:             }else{ 
  94:                 push(@passed, $email);
  95:             }
  96:         }
  97:         my $passed_report = join("\n", @passed); 
  98:         my $failed_report = join("\n", @failed); 
  99:         
 100:         #
 101:         #
 102:         ###################################################################
 103:         
 104:         print $q->p('the following emails passed mx lookup verification:')     . 
 105:               
 106:     
 107:               $q->start_form(-action => $MOJO_URL, -method => 'POST')          .  
 108:               $q->hidden('f', 'add_email'), 
 109:               $q->p($q->textarea(-name  => 'new_emails', 
 110:                                  -value => $passed_report,
 111:                                  -rows  => 5,
 112:                                  -cols  => 40)) .
 113:               $q->submit(-name  => 'Add These Addresses to Your List', 
 114:                          -style => $STYLE{green_submit})                       .
 115:               $q->end_form() .
 116:               
 117:               
 118:                       
 119:               $q->p('The following emails failed mx lookup verification:') . 
 120:               
 121:               
 122:               $q->start_form(-action => $MOJO_URL, -method => 'POST')      .
 123:               $q->hidden('f', 'delete_email'), 
 124:               $q->hidden('process', 'true'), 
 125:               $q->p($q->textarea(-name  => 'delete_list', 
 126:                                  -value => $failed_report,
 127:                                  -rows  => 5,
 128:                                  -cols  => 40,
 129:                                  )) .
 130:               $q->submit(-name => 'Remove These Addresses From Your List',
 131:                          -style => $STYLE{red_submit})                     .
 132:               $q->end_form();     
 133: 
 134: 
 135: }
 136: 
 137: 
 138: #footer
 139: print admin_html_footer(-Form => 0);

Code Explanation

Let's step through it.

   1: #!/usr/bin/perl -w
   2: use strict; 
   3: 
   4: # make sure the MOJO lib is in the lib paths!
   5: use lib qw(./ ./MOJO ../ ../MOJO); 
   6: 
   7: # use some of those Modules
   8: use MOJO::Config;
   9: use MOJO::HTML; 
  10: use MOJO::Guts;
  11: use MOJO::List;

Perl scripts start with a shebang line (#!/usr/bin/perl -w) and we've turned on the strict() pragma. Line #5 adds a few more places for Perl to look for libraries that we need and Line #8 - #11 import the Mojo Mail libraries we're going to use.

  13: # we need this for cookies things
  14: use CGI; 
  15: my $q = new CGI; 
  16: 
  17: # this is how we find what list we're working on
  18: my %login          = $q->cookie(-name => 'login'); 
  19: 
  20: my $admin_list     = $login{admin_list}               || undef; 
  21: my $admin_password = $login{admin_password}           || undef;

Login information is saved in a cookie, so you don't have to enter your login information on each screen. Line #14 and #15 imports the CGI module and creates a new CGI object. We then fetch the login cookie on line #18 and save them to variables on line #20 and #21.

  24: # This will take care of all out security woes
  25: my $root_login = check_list_security(-Admin_List      => $admin_list, 
  26:                                      -Admin_Password  => $admin_password, 
  27:                                      -IP_Address      => $ENV{REMOTE_ADDR},
  28:                                      -Function        => 'mx_lookup');
  29: my $list = $admin_list;

check_list_security is the control panel security check and will make sure that only the right people have access to this plugin. It'll also take care of alerting that something is wrong, if you do something like use a wrong password. It returns whether the root password was used to login.

  31: # get the list information
  32: my %list_info = open_database(-List => $list);

%list_info how holds all the list settings for our list.

  34: # header     
  35: print(admin_html_header(-Title      => "mx lookup Verification",
  36:                         -List       => $list_info{list},
  37:                         -Form       => 0,
  38:                         -Root_Login => $root_login));

Line #34 - #38 prints the top portion of the screen. We've told it that our title is mx lookup Verification, what our list is, that it shouldn't automatically create the start of a form, and whether we've logged in as root.

All the above code is standard fair if we want to make an admin screen, so we can use similar code for each admin screen we ever make.

  41: if(!$q->param('process')){

Line #41 starts an if/else block. process is a form field that may or may not get past. If it hasn't, we'll present the first form, to enter email addresses for verification.

  45:     print $q->p('Warning! mx lookup has not been enabled! '  .
  46:           $q->a({-href=>$MOJO_URL.'?f=list_options'}, 
  47:           $q->b('Enable...'))) 
  48:               unless $list_info{mx_check} ==1 ;

mx lookup isn't turned on by default, since it's a possibility that mx lookup capabilities aren't available on every server setup. If mx lookup hasn't been turned on, we'll print a little message and a link to the admin screen where this can be turned on.

  52:     print $q->p({-align => 'right'}, 
  53:           $q->start_form(-action => $MOJO_URL, 
  54:                          -method => 'POST',
  55:                          -target => 'text_list'),
  56:         $q->hidden('f', 'text_list'),
  57:         $q->submit(-name  => 'Open Subscription List in New Window',
  58:                    -style => $STYLE{default_submit}) .
  59:         $q->end_form());

I thought it would also be helpful if I could have easy access to my present list, so Line #52 - #59 prints a form that will pop open a new window when pressed with my current subscription list. I can then copy/paste the addresses I want to check into the next form's textarea. I'm using the CGI.pm judiciously in this example. Documentation for this module can be found at:

http://search.cpan.org/author/LDS/CGI.pm-2.89/CGI.pm

  64:     print $q->p("Paste email addresses that you would like an mx lookup for:") . 
  65:           $q->start_form()                                                     . 
  66:           $q->p(
  67:           $q->textarea(-name => 'addresses',
  68:                        -rows  => 5,
  69:                        -cols  => 40,
  70:                                 ))                                             . 
  71:           $q->hidden('process', 'true')                                        .
  72:           $q->submit(-name => 'Verify...',
  73:                     -style => $STYLE{green_submit})                                                 .
  74:           $q->end_form();

Line #64 - #74 prints a form with a textarea to allow you to type in, or paste email addresses for the mx verification. Notice that we called the textarea addresses. Once this form is submitted, we'll be able to get these values via the addresses paramater. Also notice the hidden field, process. Once this form is submitted, the script will run and present to us the next screen...

  78:     }else{

That starts after Line #78

  80:         my $lh = MOJO::List->new(-List => $list); 
  81:         my @emails = split(/\s+|,|;|\n+/, $q->param('addresses'));
  82:         my @passed;
  83:         my @failed;

Line #80 creates a new MOJO::List object, just like we did for our FormMail.pl example. Line #81 creates an array from the address param, by splitting up the string at spaces, commas, semicolons and newlines. We then initialize two new arrays, @passed and @failed on line #82 and #83.

  89:         foreach my $email(@emails){ 
  90:             my ($status, $errors) = $lh->subscription_check(-Email => $email); 
  91:             if($errors->{mx_lookup_failed} == 1){
  92:                 push(@failed, $email);    
  93:             }else{ 
  94:                 push(@passed, $email);
  95:             }
  96:         }
  97:         my $passed_report = join("\n", @passed); 
  98:         my $failed_report = join("\n", @failed);

This is the meat and potatoes of the script.

Each email address is gone through the subscription_check method and if the fail the mx lookup test, they're added to the @failed array. Otherwise, they're added to the @passed array. These two arrays are made into strings, so we can easily print them.

 104:         print $q->p('the following emails passed mx lookup verification:')     . 
 105:               
 106:     
 107:               $q->start_form(-action => $MOJO_URL, -method => 'POST')          .  
 108:               $q->hidden('f', 'add_email'), 
 109:               $q->p($q->textarea(-name  => 'new_emails', 
 110:                                  -value => $passed_report,
 111:                                  -rows  => 5,
 112:                                  -cols  => 40)) .
 113:               $q->submit(-name  => 'Add These Addresses to Your List', 
 114:                          -style => $STYLE{green_submit})                       .
 115:               $q->end_form() .

Line # 105 to #115 prints a form that will present all the passed email addresses in a textarea. I've also made it so when the form is submitted, it'll tie into the mass subscription process in the main mojo.cgi script.

 119:               $q->p('The following emails failed mx lookup verification:') . 
 120:               
 121:               
 122:               $q->start_form(-action => $MOJO_URL, -method => 'POST')      .
 123:               $q->hidden('f', 'delete_email'), 
 124:               $q->hidden('process', 'true'), 
 125:               $q->p($q->textarea(-name  => 'delete_list', 
 126:                                  -value => $failed_report,
 127:                                  -rows  => 5,
 128:                                  -cols  => 40,
 129:                                  )) .
 130:               $q->submit(-name => 'Remove These Addresses From Your List',
 131:                          -style => $STYLE{red_submit})                     .
 132:               $q->end_form();

Similarly, Line #122 - #132 prints a form that will present all the failed email addresses in a textarea. a form that will present all the passed email addresses in a textarea. I've also made it so when the form is submitted, it'll tie into the mass unsubscription process in the main mojo.cgi script.

 135: }

Line #135 closes out if/else block and,

  138: #footer
  139: print admin_html_footer(-Form => 0);

Prints the last part of our administration screen.

Save this code as mx_lookup.cgi

Installing

For convention's sake, I put all the plugins that I make for Mojo Mail in a directory called plugins, that I make in the cgi-bin/mojo directory. To install this script, upload mx_lookup.cgi into the plugins directory and set the permissions to 755. Visit the script in your web browser. If you aren't logged on to any lists, you'll see the following screen:

Because we setup our plugin the way we did, we inherited all sorts of free goodies. The check_list_security subroutine checks if our login information is correct. In this case, it isn't, so it's given us a screen where we can enter that information.

Enter the correct information and click Login

Hurray! This is the first screen of our plugin. I told it would work. As you can see, the plugin looks like any other list control panel screen. We've inherited that look as well.

As you can see, I haven't enabled mx lookups, I can Open my current list in a new window, and there is a textarea to plop in some addresses for testing.

Enable mx lookups if you haven't already, paste some email addresses in:

and click, Verify...

Works like a charm!

Adding a Menu Item

We just made a new feature in Mojo Mail with a scant bit of code. Since we've verified the plugin works, we need to add it to the admin menu. This is done by adding an entry in the $ADMIN_MENU variable, located in the Config.pm file. If you want, you can copy this variable (the whole thing, mind), into your .mojo_config file. Make sure you put the below code in the correct place. To help you out, I've added the code, but commented it out. Just find it, and uncomment it.

This is what we need to add:

 {-Title       => 'mx lookup Verification', 
   -Title_URL      => $PLUGIN_URL."/mx_lookup.cgi",
   -Function       => 'mx_lookup',
   -Activated      => 1, 
 },
-Title
This is the title that's going to be displayed in the admin menu

-Title_URL
The URL to our plugin. Since we named our plugin mx_lookup.cgi, and followed convention of putting the plugin in the mojo/plugins folding, Mojo will know where to look.

-Function
When we created out function, we passed as an argument in check_list_security:
 -Function        => 'mx_lookup'

This is sort of an id to this admin screen, since there's nothing really different about the plugin that we just made and any other screen/function in the list control panel. This help Mojo Mail tell different parts of the program apart.

-Activated
This tells Mojo Mail if it should actually activate the plugin for list owners to use.

Once this tweak is made, you should see a new entry, under plugins, in the admin menu:

The Mojo Mail Magic Book Index

The Mojo Mail Magic book is Copyright © 2003 Justin Simoni