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>
COPYRIGHT
Copyright 2008 Sinapticode - http://www.sinapticode.com
Sinapticode