added myself to the contributors line(s)
[catagits/Catalyst-View-Email.git] / lib / Catalyst / View / Email.pm
index 73bc44f..3c61149 100644 (file)
@@ -1,19 +1,49 @@
 package Catalyst::View::Email;
 
-use warnings;
-use strict;
-
-use Class::C3;
+use Moose;
 use Carp;
 
-use Email::Send;
+use Encode qw(encode decode);
+use Email::Sender::Simple qw/ sendmail /;
 use Email::MIME::Creator;
+extends 'Catalyst::View';
+
+our $VERSION = '0.13.01';
 
-use base qw/ Catalyst::View /;
+has 'mailer' => (
+    is      => 'rw',
+    isa     => 'Str',
+    lazy    => 1,
+    default => sub { "sendmail" }
+);
 
-our $VERSION = '0.09999_02';
+has 'stash_key' => (
+    is      => 'rw',
+    isa     => 'Str',
+    lazy    => 1,
+    default => sub { "email" }
+);
 
-__PACKAGE__->mk_accessors(qw/ mailer /);
+has 'default' => (
+    is      => 'rw',
+    isa     => 'HashRef',
+    default => sub { { content_type => 'text/plain' } },
+    lazy    => 1,
+);
+
+has 'sender' => (
+    is      => 'rw',
+    isa     => 'HashRef',
+    lazy    => 1,
+    default => sub { { mailer => shift->mailer } }
+);
+
+has 'content_type' => (
+    is      => 'rw',
+       isa     => 'Str',
+       default => sub { shift->default->{content_type} },
+       lazy    => 1,
+);
 
 =head1 NAME
 
@@ -21,83 +51,92 @@ Catalyst::View::Email - Send Email from Catalyst
 
 =head1 SYNOPSIS
 
-This module simply sends out email from a stash key specified in the
+This module sends out emails from a stash key specified in the
 configuration settings.
 
 =head1 CONFIGURATION
 
+WARNING: since version 0.10 the configuration options slightly changed!
+
 Use the helper to create your View:
     
     $ script/myapp_create.pl view Email Email
 
-In your app configuration (example in L<YAML>):
-
-    View::Email:
-        # Where to look in the stash for the email information.
-        # 'email' is the default, so you don't have to specify it.
-        stash_key: email
-        # Define the defaults for the mail
-        default:
-            # Defines the default content type (mime type).
-            # mandatory
-            content_type: text/plain
-            # Defines the default charset for every MIME part with the content
-            # type text.
-            # According to RFC2049 a MIME part without a charset should
-            # be treated as US-ASCII by the mail client.
-            # If the charset is not set it won't be set for all MIME parts
-            # without an overridden one.
-            # Default: none
-            charset: utf-8
-        # Setup how to send the email
-        # all those options are passed directly to Email::Send
-        sender:
-            mailer: SMTP
-            # mailer_args is passed directly into Email::Send 
-            mailer_args:
-                Host:       smtp.example.com # defaults to localhost
-                username:   username
-                password:   password
-
-=head2 NOTE ON SMTP
+In your app configuration:
+
+    __PACKAGE__->config(
+        'View::Email' => {
+            # Where to look in the stash for the email information.
+            # 'email' is the default, so you don't have to specify it.
+            stash_key => 'email',
+            # Define the defaults for the mail
+            default => {
+                # Defines the default content type (mime type). Mandatory
+                content_type => 'text/plain',
+                # Defines the default charset for every MIME part with the 
+                # content type text.
+                # According to RFC2049 a MIME part without a charset should
+                # be treated as US-ASCII by the mail client.
+                # If the charset is not set it won't be set for all MIME parts
+                # without an overridden one.
+                # Default: none
+                charset => 'utf-8'
+            },
+            # Setup how to send the email
+            # all those options are passed directly to Email::Send
+            sender => {
+                mailer => 'SMTP',
+                # mailer_args is passed directly into Email::Send 
+                mailer_args => {
+                    Host     => 'smtp.example.com', # defaults to localhost
+                    username => 'username',
+                    password => 'password',
+            }
+          }
+        }
+    );
+
+=head1 NOTE ON SMTP
 
 If you use SMTP and don't specify Host, it will default to localhost and
-attempt delivery.  This often times means an email will sit in a queue
-somewhere and not be delivered.
+attempt delivery. This often means an email will sit in a queue and
+not be delivered.
 
 =cut
 
-__PACKAGE__->config(
-    stash_key   => 'email',
-    default     => {
-        content_type    => 'text/html',
-    },
-);
-
 =head1 SENDING EMAIL
 
-In your controller, simply forward to the view after populating the C<stash_key>
+Sending email is just filling the stash and forwarding to the view:
 
     sub controller : Private {
         my ( $self, $c ) = @_;
+
         $c->stash->{email} = {
-            to      => q{catalyst@rocksyoursocks.com},
-            cc      => q{foo@bar.com},
-            bcc     => q{hidden@secret.com},
-            from    => q{no-reply@socksthatarerocked.com},
-            subject => qq{Your Subject Here},
-            body    => qq{Body Body Body}
+            to      => 'jshirley@gmail.com',
+            cc      => 'abraxxa@cpan.org',
+            bcc     => join ',', qw/hidden@secret.com hidden2@foobar.com/,
+            from    => 'no-reply@foobar.com',
+            subject => 'I am a Catalyst generated email',
+            body    => 'Body Body Body',
         };
-        $c->forward( $c->view('Email' ) );
+        
+        $c->forward( $c->view('Email') );
     }
 
-Alternatively, you can use a more raw interface, and specify the headers as
-an array reference.
+Alternatively you can use a more raw interface and specify the headers as
+an array reference like it is passed to L<Email::MIME::Creator>.
+Note that you may also mix both syntaxes if you like ours better but need to
+specify additional header attributes.
+The attributes are appended to the header array reference without overwriting
+contained ones.
 
     $c->stash->{email} = {
         header => [
-            To      => 'foo@bar.com',
-            Subject => 'Note the capitalization differences'
+            To      => 'jshirley@gmail.com',
+            Cc      => 'abraxxa@cpan.org',
+            Bcc     => join ',', qw/hidden@secret.com hidden2@foobar.com/,
+            From    => 'no-reply@foobar.com',
+            Subject => 'Note the capitalization differences',
         ],
         body => qq{Ain't got no body, and nobody cares.},
         # Or, send parts
@@ -108,22 +147,23 @@ an array reference.
                     disposition  => 'attachment',
                     charset      => 'US-ASCII',
                 },
-                body => qq{Got a body, but didn't get ahead.}
+                body => qq{Got a body, but didn't get ahead.},
             )
         ],
     };
 
 =head1 HANDLING ERRORS
 
-If the email fails to send, the view will die (throw an exception).  After
-your forward to the view, it is a good idea to check for errors:
+If the email fails to send, the view will die (throw an exception).
+After your forward to the view, it is a good idea to check for errors:
+    
+    $c->forward( $c->view('Email') );
     
-    $c->forward( $c->view('Email' ) );
     if ( scalar( @{ $c->error } ) ) {
         $c->error(0); # Reset the error condition if you need to
-        $c->res->body('Oh noes!');
+        $c->response->body('Oh noes!');
     } else {
-        $c->res->body('Email sent A-OK! (At least as far as we can tell)');
+        $c->response->body('Email sent A-OK! (At least as far as we can tell)');
     }
 
 =head1 USING TEMPLATES FOR EMAIL
@@ -132,48 +172,27 @@ Now, it's no fun to just send out email using plain strings.
 Take a look at L<Catalyst::View::Email::Template> to see how you can use your
 favourite template engine to render the mail body.
 
+=head1 METHODS
 
-=cut
+=over 4
 
-sub new {
-    my $self = shift->next::method(@_);
+=item new
 
-    my ( $c, $arguments ) = @_;
-    
-    my $stash_key = $self->{stash_key};
-    croak "$self stash_key isn't defined!"
-        if ($stash_key eq '');
+Validates the base config and creates the L<Email::Send> object for later use
+by process.
 
-    my $sender = Email::Send->new;
+=cut
 
-    if ( my $mailer = $self->{sender}->{mailer} ) {
-        croak "$mailer is not supported, see Email::Send"
-            unless $sender->mailer_available($mailer);
-        $sender->mailer($mailer);
-    } else {
-        # Default case, run through the most likely options first.
-        for ( qw/SMTP Sendmail Qmail/ ) {
-            $sender->mailer($_) and last if $sender->mailer_available($_);
-        }
-    }
+sub BUILD {
+    my $self = shift;
 
-    if ( my $args = $self->{sender}->{mailer_args} ) {
-        if ( ref $args eq 'HASH' ) {
-            $sender->mailer_args([ %$args ]);
-        }
-        elsif ( ref $args eq 'ARRAY' ) {
-            $sender->mailer_args($args);
-        } else {
-            croak "Invalid mailer_args specified, check pod for Email::Send!";
-        }
-    }
-
-    $self->mailer($sender);
+    my $stash_key = $self->stash_key;
+    croak "$self stash_key isn't defined!"
+      if ( $stash_key eq '' );
 
-    return $self;
 }
 
-=head2 process
+=item process($c)
 
 The process method does the actual processing when the view is dispatched to.
 
@@ -186,68 +205,74 @@ sub process {
     my ( $self, $c ) = @_;
 
     croak "Unable to send mail, bad mail configuration"
-        unless $self->mailer;
+      unless $self->mailer;
 
-    my $email  = $c->stash->{$self->{stash_key}};
+    my $email = $c->stash->{ $self->stash_key };
     croak "Can't send email without a valid email structure"
-        unless $email;
+      unless $email;
 
-    if ( exists $self->{content_type} ) {
-        $email->{content_type} ||= $self->{content_type};
+    # Default content type
+    if ( $self->content_type and not $email->{content_type} ) {
+        $email->{content_type} = $self->content_type;
     }
 
-    my $header  = $email->{header} || [];
-        push @$header, ('To' => delete $email->{to})
-            if $email->{to};
-        push @$header, ('Cc' => delete $email->{cc})
-            if $email->{cc};
-        push @$header, ('Bcc' => delete $email->{bcc})
-            if $email->{bcc};
-        push @$header, ('From' => delete $email->{from})
-            if $email->{from};
-        push @$header, ('Subject' => delete $email->{subject})
-            if $email->{subject};
-        push @$header, ('Content-type' => delete $email->{content_type})
-            if $email->{content_type};
+    my $header = $email->{header} || [];
+    push @$header, ( 'To' => delete $email->{to} )
+      if $email->{to};
+    push @$header, ( 'Cc' => delete $email->{cc} )
+      if $email->{cc};
+    push @$header, ( 'Bcc' => delete $email->{bcc} )
+      if $email->{bcc};
+    push @$header, ( 'From' => delete $email->{from} )
+      if $email->{from};
+    push @$header,
+      ( 'Subject' => Encode::encode( 'MIME-Header', delete $email->{subject} ) )
+      if $email->{subject};
+    push @$header, ( 'Content-type' => $email->{content_type} )
+      if $email->{content_type};
 
     my $parts = $email->{parts};
     my $body  = $email->{body};
-   
+
     unless ( $parts or $body ) {
         croak "Can't send email without parts or body, check stash";
     }
 
-    my %mime = ( header => $header );
+    my %mime = ( header => $header, attributes => {} );
 
     if ( $parts and ref $parts eq 'ARRAY' ) {
         $mime{parts} = $parts;
-    } else {
+    }
+    else {
         $mime{body} = $body;
     }
-    
-    if ( $mime{attributes} and not $mime{attributes}->{charset} and
-         $self->{default}->{charset} )
+
+    $mime{attributes}->{content_type} = $email->{content_type}
+      if $email->{content_type};
+    if (    $mime{attributes}
+        and not $mime{attributes}->{charset}
+        and $self->{default}->{charset} )
     {
         $mime{attributes}->{charset} = $self->{default}->{charset};
     }
 
     my $message = $self->generate_message( $c, \%mime );
 
-    #my $message = Email::MIME->create(%mime);
+    if ($message) {
+        my $return = sendmail( $message, { transport => $self->mailer } );
 
-    if ( $message ) {
-        my $return = $self->mailer->send($message);
         # return is a Return::Value object, so this will stringify as the error
-        # in the case of a failure.  
+        # in the case of a failure.
         croak "$return" if !$return;
-    } else {
+    }
+    else {
         croak "Unable to create message";
     }
 }
 
-=head2 setup_attributes
+=item setup_attributes($c, $attr)
 
-Merge attributes with the configured defaults.  You can override this method to
+Merge attributes with the configured defaults. You can override this method to
 return a structure to pass into L<generate_message> which subsequently
 passes the return value of this method to Email::MIME->create under the
 C<attributes> key.
@@ -256,32 +281,43 @@ C<attributes> key.
 
 sub setup_attributes {
     my ( $self, $c, $attrs ) = @_;
-    
-    my $default_content_type    = $self->{default}->{content_type};
-    my $default_charset         = $self->{default}->{charset};
+
+    my $default_content_type = $self->default->{content_type};
+    my $default_charset      = $self->default->{charset};
 
     my $e_m_attrs = {};
 
-    if (exists $attrs->{content_type} && defined $attrs->{content_type} && $attrs->{content_type} ne '') {
-        $c->log->debug('C::V::Email uses specified content_type ' . $attrs->{content_type} . '.') if $c->debug;
+    if (   exists $attrs->{content_type}
+        && defined $attrs->{content_type}
+        && $attrs->{content_type} ne '' )
+    {
+        $c->log->debug( 'C::V::Email uses specified content_type '
+              . $attrs->{content_type}
+              . '.' )
+          if $c->debug;
         $e_m_attrs->{content_type} = $attrs->{content_type};
     }
-    elsif (defined $default_content_type && $default_content_type ne '') {
-        $c->log->debug("C::V::Email uses default content_type $default_content_type.") if $c->debug;
+    elsif ( defined $default_content_type && $default_content_type ne '' ) {
+        $c->log->debug(
+            "C::V::Email uses default content_type $default_content_type.")
+          if $c->debug;
         $e_m_attrs->{content_type} = $default_content_type;
     }
-   
-    if (exists $attrs->{charset} && defined $attrs->{charset} && $attrs->{charset} ne '') {
+
+    if (   exists $attrs->{charset}
+        && defined $attrs->{charset}
+        && $attrs->{charset} ne '' )
+    {
         $e_m_attrs->{charset} = $attrs->{charset};
     }
-    elsif (defined $default_charset && $default_charset ne '') {
+    elsif ( defined $default_charset && $default_charset ne '' ) {
         $e_m_attrs->{charset} = $default_charset;
     }
 
     return $e_m_attrs;
 }
 
-=head2 generate_message($c, $attr)
+=item generate_message($c, $attr)
 
 Generate a message part, which should be an L<Email::MIME> object and return it.
 
@@ -293,11 +329,33 @@ message object.
 sub generate_message {
     my ( $self, $c, $attr ) = @_;
 
-    # setup the attributes (merge with defaults)
-    $attr->{attributes} = $self->setup_attributes($c, $attr->{attributes});
-    return Email::MIME->create(%$attr);
+    # setup the attributes (merge with defaultis)
+       $attr->{attributes} = $self->setup_attributes($c, $attr->{attributes});
+    Email::MIME->create(
+           %$attr
+       );
 }
 
+=back
+
+=head1 TROUBLESHOOTING
+
+As with most things computer related, things break.  Email even more so.  
+Typically any errors are going to come from using SMTP as your sending method,
+which means that if you are having trouble the first place to look is at
+L<Email::Send::SMTP>.  This module is just a wrapper for L<Email::Send>,
+so if you get an error on sending, it is likely from there anyway.
+
+If you are using SMTP and have troubles sending, whether it is authentication
+or a very bland "Can't send" message, make sure that you have L<Net::SMTP> and,
+if applicable, L<Net::SMTP::SSL> installed.
+
+It is very simple to check that you can connect via L<Net::SMTP>, and if you
+do have sending errors the first thing to do is to write a simple script
+that attempts to connect.  If it works, it is probably something in your
+configuration so double check there.  If it doesn't, well, keep modifying
+the script and/or your mail server configuration until it does!
+
 =head1 SEE ALSO
 
 =head2 L<Catalyst::View::Email::Template> - Send fancy template emails with Cat
@@ -310,6 +368,8 @@ sub generate_message {
 
 J. Shirley <jshirley@gmail.com>
 
+Alexander Hartmaier <abraxxa@cpan.org>
+
 =head1 CONTRIBUTORS
 
 (Thanks!)
@@ -322,7 +382,15 @@ Simon Elliott <cpan@browsing.co.uk>
 
 Roman Filippov
 
-Alexander Hartmaier <alex_hartmaier@hotmail.com>
+Lance Brown <lance@bearcircle.net>
+
+Devin Austin <dhoss@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2007 - 2009
+the Catalyst::View::Email L</AUTHORS> and L</CONTRIBUTORS>
+as listed above.
 
 =head1 LICENSE