Getting the word out with Catalyst::View::Email

I don't think you'll find many web applications that don't need to send emails. You know the common patterns: confirmation emails, password recovery emails, monthly newsletter, here's your order, invoice attached. The works.

Catalyst::View::Email is the standard way to send email from Catalyst apps. So you can start removing that nasty email sending code from your controllers now ;)

Getting started

Well, first things first, install Catalyst::View::Email from CPAN.

Now let's start building our app. Not being very original, I decided to go with a Christmas theme: the app will allow people to let their would-be Santa know what they want for Christmas. So we'll need the user's email and name, Santa's email and a description for the gift. Wrap all this in an email and deliver to Santa :)

Obviously I'll just focus on the email part, so I'll leave important stuff like input validation or DOS protection (you don't want some kiddie sending gazillions of emails through your app) as an exercise to the reader.

Here goes:

 catalyst.pl SantaLetter
 cd SantaLetter
 script/santaletter_create view TT TT

Add a wrapper option in your TT.pm

 __PACKAGE__->config(WRAPPER => 'wrapper.tt');

and create root/wrapper.tt

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
        <meta http-equiv="content-type" content="text/html;charset=iso-8859-1" />
        <title>Instant Santa lettery delivery service</title>
        <style type="text/css"> label,textarea { display: block }
 </style>
 </head>
 <body>
 [% content %]
 </body>
 </html>

Now create a simple form in root/index.tt:

 <h1>Let Santa know what you want :)</h1>
 <form method="post" action="[% c.uri_for('/') %]">
     <fieldset><legend>Tell me everything</legend>
         <label>Your email:<input type="text" name="email"/></label>
         <label>Your name:<input type="text" name="name"/></label>
         <label>Your Santa's email:<input type="text" name="santa"/></label>
         <label>Your wish:<textarea name="gift" rows="5" cols="40"></textarea></label>
         <input type="submit" name="submit" value="Go!"/>
     </fieldset>
 </form>

Remove the welcome message line in the index action from Root.pm and you can admire the form at http://localhost:3000

Let's send some emails

Create the view:

 script/santaletter_create.pl view Email Email

If you have an exotic setup, you can tweak the config options. By default it will use the local mail setup, you may want to use an external SMTP server for instance.

Don't forget to set TT as default view in santaletter.conf:

 default_view TT

Everything is set up, so let's write the controller code, in Root.pm:

 sub index :Path :Args(0) {
     my ( $self, $c ) = @_;

     return unless $c->req->method eq 'POST';
     # TODO input validation

     $c->stash->{email} = {
         to => $c->req->param('santa'),
         from  => $c->req->param('email'),
         subject => sprintf( "Letter for santa from %s",  $c->req->param('name')),
         body => sprintf( "Hey Santa,\n\nYou should grant this Christmas wish for %s:\n\n%s\n-- \nMerry X-mas,\nSanta's helpers",
                     $c->req->param('name'),$c->req->param('gift'))
     };
     $c->forward( $c->view('Email') );
     $c->res->redirect($c->req->uri_with({success=>1}));

 }

After the POST is handled, we redirect back, but it would be useful to show some feedback. Add this in index.tt:

 [% IF c.req.param('success') %] <p>Your wish is on its way to Santa!</p> [% END %]




Using an email template

So now you know how to quickly send emails from Catalyst. But stuffing that message body in a string is rather dirty. This is where Catalyst::View::Email::Template comes handy. It will use your default view to render a template, assemble a multi-part email using Email::MIME::Creator and send it out.

So let's give it a spin:

 script/santaletter_create.pl view Email::Template Email::Template

We'll create a second TT view, without a wrapper:

 script/santaletter_create.pl view TT::NoWrap TT

And tell Email::Template to use it by adding this in its config:

 default => { view => 'TT::NoWrap' }

The new controller code is:

 sub index :Path :Args(0) {
     my ( $self, $c ) = @_;

     return unless $c->req->method eq 'POST';
     # TODO input validation

     $c->stash->{email} = {
         to => $c->req->param('santa'),
         from  => $c->req->param('email'),
         subject => sprintf( "Letter for santa from %s",  $c->req->param('name')),
         content_type => 'multipart/alternative',
         template=> 'email.tt'
     };
     $c->forward( $c->view('Email::Template') );
     $c->res->redirect($c->req->uri_with({success=>1}));

 }




And here's the email template in root/email.tt :

 Hey Santa,

 You should grant this Christmas wish for [% c.req.params.name %]:

 [% c.req.params.gift %]

 --
 Merry X-mas,
 Santa's helpers

Now you can change your email templates without touching the controller code. Neat!

What now?

You probably want more features, like sending HTML email or attaching files. Catalyst::View::Email handles these things well: instead of directly setting the email body, you can pass an arrayref with Email::Mime parts , and there's a nice example in the documentation.

So go read the docs and learn about the other features, and you can always bug us on IRC ( irc.perl.org , #catalyst) if things don't work out.

AUTHOR

Bogdan Lucaciu <bogdan@sinapticode.com>