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';
-use base qw|Catalyst::View|;
+our $VERSION = '0.15';
-our $VERSION = '0.02';
+has 'mailer' => (
+ is => 'rw',
+ isa => 'Str',
+ lazy => 1,
+ default => sub { "sendmail" }
+);
-__PACKAGE__->mk_accessors(qw(sender stash_key content_type mailer));
+has 'stash_key' => (
+ is => 'rw',
+ isa => 'Str',
+ lazy => 1,
+ default => sub { "email" }
+);
+
+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
=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
-In your app configuration (example in L<YAML>):
+WARNING: since version 0.10 the configuration options slightly changed!
- View::Email:
- stash_key: email
- content_type: text/plain
- sender:
- method: SMTP
- # mailer_args is passed directly into Email::Send
- mailer_args:
- - Host: smtp.example.com
- - username: username
- - password: password
+Use the helper to create your View:
+
+ $ script/myapp_create.pl view Email Email
+
+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',
+ }
+ }
+ }
+ );
-=cut
+=head1 NOTE ON SMTP
-__PACKAGE__->config(
- stash_key => 'email',
-);
+If you use SMTP and don't specify Host, it will default to localhost and
+attempt delivery. This often means an email will sit in a queue and
+not be delivered.
+
+=cut
=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},
- 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('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
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 FAILURES
+=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('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 OTHER MAILERS
+=head1 USING TEMPLATES FOR EMAIL
+
+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.
-Now, it's no fun to just send out email using plain strings. We also
-have L<Catalyst::View::Email::Template> for use. You can also toggle
-this as being used by setting up your configuration to look like this:
+=head1 METHODS
- View::Email:
- default_view: TT
+=over 4
-Then, Catalyst::View::Email will forward to your View::TT by default.
+=item new
+
+Validates the base config and creates the L<Email::Send> object for later use
+by process.
=cut
-sub new {
- my $self = shift->next::method(@_);
+sub BUILD {
+ my $self = shift;
- my ( $c, $arguments ) = @_;
+ my $stash_key = $self->stash_key;
+ croak "$self stash_key isn't defined!"
+ if ( $stash_key eq '' );
- my $mailer = Email::Send->new;
+}
- if ( my $method = $self->sender->{method} ) {
- croak "$method is not supported, see Email::Send"
- unless $mailer->mailer_available($method);
- $mailer->mailer($method);
- } else {
- # Default case, run through the most likely options first.
- for ( qw/SMTP Sendmail Qmail/ ) {
- $mailer->mailer($_) and last if $mailer->mailer_available($_);
- }
- }
+=item process($c)
- if ( $self->sender->{mailer_args} ) {
- $mailer->mailer_args($self->sender->{mailer_args});
- }
+The process method does the actual processing when the view is dispatched to.
- $self->mailer($mailer);
+This method sets up the email parts and hands off to L<Email::Send> to handle
+the actual email delivery.
- return $self;
-}
+=cut
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;
-
- if ( $self->content_type ) {
- $email->{content_type} ||= $self->content_type;
+ unless $email;
+
+ # 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, ('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;
}
- my $message = Email::MIME->create(%mime);
+ $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};
+ }
- if ( $message ) {
- $self->mailer->send($message);
- } else {
+ my $message = $self->generate_message( $c, \%mime );
+
+ if ($message) {
+ my $return = sendmail( $message, { transport => $self->mailer } );
+
+ # return is a Return::Value object, so this will stringify as the error
+ # in the case of a failure.
+ croak "$return" if !$return;
+ }
+ else {
croak "Unable to create message";
}
}
+=item setup_attributes($c, $attr)
+
+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.
+
+=cut
+
+sub setup_attributes {
+ my ( $self, $c, $attrs ) = @_;
+
+ 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;
+ $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;
+ $e_m_attrs->{content_type} = $default_content_type;
+ }
+
+ if ( exists $attrs->{charset}
+ && defined $attrs->{charset}
+ && $attrs->{charset} ne '' )
+ {
+ $e_m_attrs->{charset} = $attrs->{charset};
+ }
+ elsif ( defined $default_charset && $default_charset ne '' ) {
+ $e_m_attrs->{charset} = $default_charset;
+ }
+
+ return $e_m_attrs;
+}
+
+=item generate_message($c, $attr)
+
+Generate a message part, which should be an L<Email::MIME> object and return it.
+
+Takes the attributes, merges with the defaults as necessary and returns a
+message object.
+
+=cut
+
+sub generate_message {
+ my ( $self, $c, $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
J. Shirley <jshirley@gmail.com>
+Alexander Hartmaier <abraxxa@cpan.org>
+
=head1 CONTRIBUTORS
(Thanks!)
+Matt S Trout
+
Daniel Westermann-Clark
+Simon Elliott <cpan@browsing.co.uk>
+
+Roman Filippov
+
+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
This library is free software, you can redistribute it and/or modify it under