File Coverage

blib/lib/Email/Mailer.pm
Criterion Covered Total %
statement 79 83 95.1
branch 26 32 81.2
condition 24 44 54.5
subroutine 13 13 100.0
pod 2 2 100.0
total 144 174 82.7


line stmt bran cond sub pod time code
1             package Email::Mailer;
2             # ABSTRACT: Multi-purpose emailer for HTML, auto-text, attachments, and templates
3              
4 1     1   220454 use 5.014;
  1         8  
5 1     1   427 use exact -noautoclean;
  1         30245  
  1         5  
6              
7 1     1   2486 use Email::MessageID;
  1         642  
  1         31  
8 1     1   507 use Email::MIME 1.940;
  1         22506  
  1         32  
9 1     1   515 use Email::MIME::CreateHTML;
  1         90834  
  1         51  
10 1     1   460 use Email::Sender::Simple 'sendmail';
  1         162705  
  1         7  
11 1     1   728 use HTML::FormatText;
  1         19970  
  1         29  
12 1     1   676 use HTML::TreeBuilder;
  1         6595  
  1         10  
13 1     1   497 use IO::All 'io';
  1         9496  
  1         7  
14 1     1   83 use MIME::Words 'encode_mimewords';
  1         2  
  1         1492  
15              
16             our $VERSION = '1.18'; # VERSION
17              
18             sub new {
19 14     14 1 34755 my $self = shift;
20              
21 14 100       39 unless ( ref $self ) {
22             # $self is not an object, is incoming pair values = make $self object
23 8         44 $self = bless( {@_}, $self );
24             }
25             else {
26             # $self is an object = make a new $self object incorporating any new values
27 6         37 $self = bless( { %$self, @_ }, ref $self );
28             }
29              
30             # for a certain set of keys, ensure they are all lower-case
31             $self->{ lc $_ } = delete $self->{$_}
32 14 100       57 for ( grep { /^(?:to|from|subject|html|text)$/i and /[A-Z]/ } keys %$self );
  62         298  
33              
34 14         71 return $self;
35             }
36              
37             sub send {
38 8     8 1 11177 my $self = shift;
39              
40             # if @_ is a set of hashrefs, map them into new mail objects; otherwise, just merge in new values;
41             # then iterate through the objects inside the map
42             my @mails = map {
43             # make a clean copy of the data so we can return the mail object unchanged at the end
44 9         36 my $mail = {%$_};
45              
46             # process any template functionality (look for values that are scalarrefs)
47 9 100       40 if ( ref $mail->{process} eq 'CODE' ) {
48 2         23 $mail->{$_} = $mail->{process}->( ${ $mail->{$_} }, $mail->{data} || {} )
49 1   50     4 for ( grep { ref $mail->{$_} eq 'SCALAR' } keys %$mail );
  6         12  
50             }
51              
52             # automatically create the text version from HTML if there is no text version and there is HTML
53 9 100 100     56 if ( $mail->{html} and not $mail->{text} ) {
54 6   100     20 my $width = $mail->{width} // 72;
55 6   100     25 $width ||= 1_000_000;
56              
57             $mail->{text} = HTML::FormatText
58             ->new( leftmargin => 0, rightmargin => $width )
59 6         52 ->format( HTML::TreeBuilder->new->parse( $mail->{html} ) );
60             }
61              
62 9   50     17508 $mail->{'Content-Transfer-Encoding'} //= 'quoted-printable';
63 9   50     48 $mail->{'Content-Type'} ||= 'text/plain; charset=us-ascii';
64              
65 9 50       76 my $charset = ( $mail->{'Content-Type'} =~ /\bcharset\s*=\s*([^;]+)/i ) ? $1 : 'ISO-8859-1';
66 9         39 my @keys = keys %$mail;
67 9         24 for my $name ( qw( to from subject ) ) {
68 27         43 my ($key) = grep { lc($_) eq $name } @keys;
  216         309  
69             $mail->{$key} = encode_mimewords( $mail->{$key}, Charset => $charset )
70 27 50 33     144 if ( $key and defined $mail->{$key} and $mail->{$key} =~ /[^[:ascii:]]/ );
      33        
71             }
72              
73 9   33     78 $mail->{'Message-Id'} //= Email::MessageID->new->in_brackets;
74              
75             # create a headers hashref (delete things from a data copy that known to not be headers)
76             my $headers = [
77             map {
78 54 50       98 $mail->{$_} = join( ',', @{ $mail->{$_} } ) if ( ref $mail->{$_} eq 'ARRAY' );
  0         0  
79 54 50       93 $mail->{$_} = join( ',', values %{ $mail->{$_} } ) if ( ref $mail->{$_} eq 'HASH' );
  0         0  
80 54         121 ucfirst($_) => $mail->{$_};
81             }
82 9         2025 grep { not /^(?:html|text|embed|attachments|process|data|transport|width)$/i }
  81         203  
83             sort keys %$mail
84             ];
85              
86             # build up an attachments arrayref of attachment MIME objects
87             my $attachments = ( not $mail->{attachments} or ref $mail->{attachments} ne 'ARRAY' ) ? [] : [
88             map {
89             Email::MIME->create(
90             attributes => {
91             disposition => 'attachment',
92             content_type => $_->{ctype} || 'application/octet-stream',
93             encoding => $_->{encoding} // 'base64',
94             filename => $_->{name} || $_->{filename} || $_->{source},
95             name => $_->{name} || $_->{filename} || $_->{source},
96             },
97 2 100 50     2152 body => ( ( $_->{content} ) ? $_->{content} : io( $_->{source} )->binary->all ),
      50        
      33        
      33        
98             ),
99 9 100 66     48 } @{ $mail->{attachments} }
  1         3  
100             ];
101              
102             # build a single MIME email object to send based on what data we have for the email
103 9         1415 my $email_mime;
104 9 100 66     73 if ( $mail->{text} and not $mail->{html} and @$attachments == 0 ) {
    50 66        
      33        
105             $email_mime = Email::MIME->create(
106             header_str => $headers,
107             body => $mail->{text},
108 1         11 );
109             }
110             elsif ( $mail->{text} and not $mail->{html} ) {
111             $email_mime = Email::MIME->create(
112             header_str => $headers,
113             attributes => { content_type => 'multipart/mixed' },
114             parts => [
115             Email::MIME->create(
116 0         0 header_str => [ map { $_ => $mail->{$_} } 'Content-Transfer-Encoding', 'Content-Type' ],
117             body => $mail->{text},
118 0         0 ),
119             @$attachments,
120             ],
121             );
122             }
123             else {
124             my $html_email = Email::MIME->create_html(
125             header => [],
126             body => $mail->{html},
127             text_body => $mail->{text},
128             embed => $mail->{embed},
129 8         72 );
130              
131             $html_email->walk_parts( sub {
132 26     26   3089 my ($part) = @_;
133 26 100       55 return if $part->subparts;
134              
135 17 100       143 if ( $part->content_type eq 'text/plain' ) {
136 8         339 $part->charset_set($charset);
137 8         1433 $part->encoding_set( $mail->{'Content-Transfer-Encoding'} );
138             }
139 8         93357 } );
140              
141 8         13688 $email_mime = Email::MIME->create(
142             header_str => $headers,
143             attributes => { content_type => 'multipart/mixed' },
144             parts => [ $html_email, @$attachments ],
145             );
146             }
147              
148             # send the email with Email::Sender::Simple
149 9         38485 sendmail( $email_mime, { transport => $mail->{transport} } );
150              
151 9         87 $_;
152 8 100       41 } ( ref $_[0] eq 'HASH' ) ? ( map { $self->new(%$_) } @_ ) : $self->new(@_);
  2         8  
153              
154             # return the mail objects as desired by the caller
155 8 50       46 return ( wantarray() ) ? (@mails) : \@mails;
156             }
157              
158             1;
159              
160             __END__