File Coverage

blib/lib/Mojolicious/Plugin/EmailMailer.pm
Criterion Covered Total %
statement 101 115 87.8
branch 12 38 31.5
condition 13 23 56.5
subroutine 20 21 95.2
pod 1 1 100.0
total 147 198 74.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::EmailMailer;
2 1     1   1425227 use Mojo::Base 'Mojolicious::Plugin', -signatures;
  1         4  
  1         14  
3 1     1   503 use Mojo::Util qw(encode md5_sum);
  1         3  
  1         102  
4 1     1   9 use Carp;
  1         31  
  1         82  
5 1     1   872 use Email::Mailer;
  1         472719  
  1         92  
6 1     1   12 use Email::Sender::Util;
  1         3  
  1         45  
7 1     1   646 use Hash::Merge qw(merge);
  1         5993  
  1         209  
8 1     1   12 use MIME::Words qw(encode_mimeword);
  1         2  
  1         77  
9 1     1   6 use Try::Tiny;
  1         16  
  1         83  
10              
11             our $VERSION = '0.04';
12              
13 1   50 1   4 use constant TEST => $ENV{MOJO_MAIL_TEST} || 0;
  1         2  
  1         70  
14 1     1   4 use constant FROM => 'test-emailmailer-plugin@mojolicio.us';
  1         2  
  1         1662  
15              
16             my $plugin_conf = {};
17 1     1 1 61 sub register ($self, $app, $conf = {}) {
  1         2  
  1         1  
  1         2  
  1         2  
18 1   50     4 $conf->{from} //= FROM;
19 1   33     8 $conf->{'X-Mailer'} //= join ' ', 'Mojolicious', $Mojolicious::VERSION, __PACKAGE__, $VERSION, '(Perl)';
20              
21 1 50       2 if ($conf->{how}) {
22 1   50     3 my $howargs = delete($conf->{howargs}) // {};
23             $conf->{transport} = Email::Sender::Util->easy_transport(
24             $self->_normalize_transport_name(
25             delete($conf->{how})
26 1         3 ) => $howargs
27             );
28             }
29 1         7674 $conf->{transport} = Email::Sender::Util->easy_transport('Test' => {}) if TEST;
30              
31 1         9450 $plugin_conf = $conf;
32              
33 1         38 $app->helper(send_mail => \&_send_mail);
34 1         141 $app->helper(send_multiple_mail => \&_send_multiple_mail);
35 1         93 $app->helper(render_mail => \&_render_mail);
36             }
37              
38 5     5   148623 sub _send_mail ($c, %args) {
  5         13  
  5         33  
  5         9  
39 5 100 100     43 %args = %{_text_encoding(%args)} if (defined($args{text}) && !defined($args{html}));
  2         14  
40              
41             try {
42 5     5   312 return Email::Mailer->send(%{merge(\%args, $plugin_conf)})->[0];
  5         23  
43             }
44             catch {
45 1     1   3475 $c->app->log->error("[Mojolicious::Plugin::EmailMailer] There was an error while sending an email through send_mail helper. $_");
46 1         93 return 0;
47             }
48 5         94 }
49              
50 3     3   152654 sub _send_multiple_mail ($c, %args) {
  3         10  
  3         17  
  3         7  
51 3 100 100     42 return 0 unless (defined($args{mail}) && defined($args{send}));
52              
53 1 50 33     12 $args{mail} = _text_encoding(%{$args{mail}}) if (defined($args{mail}->{text}) && !defined($args{mail}->{html}));
  1         11  
54              
55 1         3 for my $mail (@{$args{send}}) {
  1         5  
56 3 50 33     14 $mail = _text_encoding(%{$mail}) if (defined($mail->{text}) && !defined($mail->{html}));
  0         0  
57             }
58              
59             try {
60 1     1   73 return Email::Mailer->new(%{merge($args{mail}, $plugin_conf)})->send(@{$args{send}});
  1         7  
  1         233  
61             }
62             catch {
63 0     0   0 $c->app->log->error("[Mojolicious::Plugin::EmailMailer] There was an error while sending an email with send_multiple_mail helper. $_");
64 0         0 return 0;
65             }
66 1         19 }
67              
68 2     2   38049 sub _render_mail ($c, @args) {
  2         15  
  2         5  
  2         4  
69 2         7 my $bytestream = $c->render_to_string(@args, format => 'mail');
70 2 50       5855 return $bytestream->to_string if $bytestream;
71 0         0 return;
72             }
73              
74 1     1   2 sub _normalize_transport_name ($c, $class = '') {
  1         1  
  1         32  
  1         2  
75 1         2 my $lower = lc($class);
76             # Sorted that according to the probability of use
77 1 50       15 return 'Sendmail' if $lower eq 'sendmail';
78 0 0       0 return 'SMTP' if $lower eq 'smtp';
79 0 0       0 return 'SMTP::Persistent' if $lower eq 'smtp::persistent';
80 0 0       0 return 'Maildir' if $lower eq 'maildir';
81 0 0       0 return 'Mbox' if $lower eq 'mbox';
82 0 0       0 return 'Print' if $lower eq 'print';
83 0 0       0 return 'Wrapper' if $lower eq 'wrapper';
84 0 0       0 return 'Test' if $lower eq 'test';
85 0 0       0 return 'DevNull' if $lower eq 'devnull';
86 0 0       0 return 'Failable' if $lower eq 'failable';
87 0         0 return $class;
88             }
89              
90 3     3   9 sub _text_encoding (%args) {
  3         13  
  3         7  
91 3         20 my $ct = _header_key('Content-Type', %args);
92 3         11 my $cte = _header_key('Content-Transfer-Encoding', %args);
93 3 50       15 $args{'Content-Type'} = 'text/plain; charset=utf8' unless defined $ct;
94 3 50       15 $args{'Content-Transfer-Encoding'} = 'quoted-printable' unless defined $cte;
95              
96 3   50     19 $ct //= 'Content-Type';
97 3         53 (my $encoding = $args{$ct}) =~ s/.*charset=([^;]+);?.*/$1/;
98 3 50       13 $args{text} = encode($encoding, $args{text}) unless $encoding eq 'utf8';
99              
100 3         17 return \%args;
101             }
102              
103 6     6   12 sub _header_key ($search, %args) {
  6         14  
  6         18  
  6         11  
104 6         14 $search = lc($search);
105 6         18 my ($key) = grep { lc($_) eq $search } keys %args;
  28         49  
106 6         20 return $key;
107             }
108              
109             1;
110              
111             =encoding utf8
112              
113             =head1 NAME
114              
115             Mojolicious::Plugin::EmailMailer - Mojolicious Plugin to send mail through Email::Mailer.
116              
117             =head1 SYNOPSIS
118              
119             # Mojolicious
120             $self->plugin('EmailMailer');
121              
122             # Mojolicious with config
123             $self->plugin('EmailMailer' => {
124             from => 'example@example.org',
125             how => 'smtp',
126             howargs => {
127             hosts => [ 'smtp.example.org' ],
128             ssl => 1, # can be 'starttls'
129             sasl_username => 'user_login',
130             sasl_password => 's3cr3t'
131             }
132             });
133              
134             # Mojolicious::Lite
135             plugin 'EmailMailer';
136              
137             # Mojolicious::Lite with config
138             plugin 'EmailMailer' => {
139             from => 'example@example.org',
140             how => 'smtp',
141             howargs => {
142             hosts => [ 'smtp.example.org' ],
143             ssl => 1, # can be 'starttls'
144             sasl_username => 'user_login',
145             sasl_password => 's3cr3t'
146             }
147             }
148              
149             =head1 DESCRIPTION
150              
151             L is a L plugin to send mail through Email::Mailer.
152              
153             Inspired by L, I needed to be able to send mail through a server which uses C.
154              
155             =head1 CONFIGURATION
156              
157             All parameters are optional.
158              
159             Except for C and C, the configuration parameters are parameters for L’s C method.
160             See L for available parameters. Those parameters will be the default
161             ones and can be overwritten when using C and C helpers (see below).
162              
163             As for C and C parameters, they are used to choose the transport for the mails (C, a SMTP server…).
164             The C parameter can be:
165              
166             =over 2
167              
168             =item DevNull - happily throw away your mail
169              
170             =item Failable - a wrapper to makes things fail predictably
171              
172             =item Maildir - deliver mail to a maildir on disk
173              
174             =item Mbox - deliver mail to an mbox on disk
175              
176             =item Print - print email to a filehandle (like stdout)
177              
178             =item SMTP - send email over SMTP
179              
180             =item SMTP::Persistent - an SMTP client that stays online
181              
182             =item Sendmail - send mail via sendmail(1)
183              
184             =item Test - deliver mail in memory for testing
185              
186             =item Wrapper - a mailer to wrap a mailer for mailing mail
187              
188             =back
189              
190             Note that the C parameter is case-insensitive.
191              
192             When giving a C parameter, the transport will be an instance of C, constructed with
193             C as parameters.
194              
195             See L to find the available parameters for the transport you want to use.
196              
197             =head1 HELPERS
198              
199             L contains three helpers: C, C and C.
200              
201             =head2 send_mail
202              
203             Straightly send a mail, according to the given arguments and plugin configuration.
204              
205             $self->send_mail(
206             to => 'test@example.org',
207             from => 'test@example.org',
208             'reply-to' => 'reply_to+test@example.org',
209             cc => '..',
210             bcc => '..',
211             subject => 'Test',
212             text => 'use Perl or die;',
213             html => '

use Perl or die;

',
214             );
215              
216             See L for available parameters.
217              
218             If C succeeds, it'll return an instantiated L object based on the combined parameters.
219             If it fails, it will return 0 and create a log error message;
220              
221             All parameters, will be used as mail headers, except the following ones:
222              
223             =over 2
224              
225             =item html
226              
227             =item text
228              
229             =item embed
230              
231             =item attachments
232              
233             =item process
234              
235             =item data
236              
237             =item transport
238              
239             =item width
240              
241             =back
242              
243             Note that the C, C and C headers will be automatically UTF-8 encoded by the plugin, then encoded as mimewords
244             by L.
245              
246             When sending a text-only mail (with or without attachments), the default values of C and C
247             headers are respectively C and C and the text is encoded according to the charset
248             specified in the C header;
249              
250             =head2 send_multiple_mail
251              
252             L allows to prepare a mail and send it more than one time, with different overriden parameters:
253              
254             Email::Mailer->new(
255             from => $from,
256             subject => $subject,
257             html => $html
258             )->send(
259             { to => 'person_0@example.com' },
260             { to => 'person_1@example.com' },
261             {
262             to => 'person_2@example.com',
263             subject => 'Override $subject with this',
264             }
265             );
266              
267             You can do the same with C:
268              
269             $self->send_multiple_mail(
270             mail => {
271             from => $from,
272             subject => $subject,
273             html => $html
274             },
275             send => [
276             { to => 'person_0@example.com' },
277             { to => 'person_1@example.com' },
278             {
279             to => 'person_2@example.com',
280             subject => 'Override $subject with this',
281             }
282             ]
283             );
284              
285             C, a hashref, obviously contains the Cnew()> arguments and C, an arrayref,
286             contains the Csend()> arguments.
287              
288             If C succeeds, it'll return an array or arrayref (based on context) of the L
289             objects ultimately created.
290             If it fails, it will return 0 and create a log error message;
291              
292             Note that the subject will be UTF-8 encoded, then encoded as mimeword, like this:
293              
294             use MIME::Words qw(encode_mimeword);
295             $subject = encode_mimeword(encode('UTF-8', $subject), 'q', 'UTF-8');
296              
297             When sending a text-only mail (with or without attachments), the default values of C and C
298             headers are respectively C and C and the text is encoded according to the charset
299             specified in the C header;
300              
301             =head3 render_mail
302              
303             my $data = $self->render_mail('user/signup');
304              
305             # or use stash params
306             my $data = $self->render_mail(template => 'user/signup', user => $user);
307              
308             Render mail template and return data, mail template format is I, i.e. I.
309              
310             =head1 EXAMPLES
311              
312             my ($to, $from, $subject, $text, $html);
313              
314             # send a simple text email
315             $self->send_mail(
316             to => $to,
317             from => $from,
318             subject => $subject,
319             text => $text
320             );
321              
322             # send multi-part HTML/text email with the text auto-generated from the HTML
323             # and images and other resources embedded in the email
324             $self->send_mail(
325             to => $to,
326             from => $from,
327             subject => $subject,
328             html => $html
329             );
330              
331             # send multi-part HTML/text email with the text auto-generated from the HTML
332             # but skip embedding images and other resources
333             $self->send_mail(
334             to => $to,
335             from => $from,
336             subject => $subject,
337             html => $html,
338             embed => 0
339             );
340              
341             # send multi-part HTML/text email but supply the text explicitly
342             $self->send_mail(
343             to => $to,
344             from => $from,
345             subject => $subject,
346             html => $html,
347             text => $text
348             );
349              
350             # send multi-part HTML/text email with a couple of attached files
351             use IO::All 'io';
352             $self->send_mail(
353             to => $to,
354             from => $from,
355             subject => $subject,
356             html => $html,
357             text => $text,
358             attachments => [
359             {
360             ctype => 'application/pdf',
361             source => 'file.pdf',
362             },
363             {
364             ctype => 'application/pdf',
365             content => io('file.pdf')->binary->all,
366             encoding => 'base64',
367             name => 'file.pdf',
368             },
369             ],
370             );
371              
372             # build an email and iterate over a data set for sending
373             $self->send_multiple_mail(
374             mail => {
375             from => $from,
376             subject => $subject,
377             html => $html
378             },
379             send => [
380             { to => 'person_0@example.com' },
381             { to => 'person_1@example.com' },
382             {
383             to => 'person_2@example.com',
384             subject => 'Override $subject with this',
385             }
386             ]
387             );
388              
389             # setup a second mail object based on the first but changing the "from"
390             my $mail_0 = $self->send_mail(
391             from => $from,
392             subject => $subject,
393             html => $html
394             );
395             if ($mail_0) {
396             my $mail_1 = $mail_0->new(from => 'different_address@example.com');
397             $mail_1->send;
398             }
399              
400             =head1 METHODS
401              
402             L inherits all methods from
403             L and implements the following new ones.
404              
405             =head2 register
406              
407             $plugin->register(Mojolicious->new);
408              
409             Register plugin in L application.
410              
411             =head1 BUGS and SUPPORT
412              
413             The latest source code can be browsed and fetched at:
414              
415             https://framagit.org/fiat-tux/mojolicious/mojolicious-plugin-emailmailer
416             git clone https://framagit.org/fiat-tux/mojolicious/mojolicious-plugin-emailmailer.git
417              
418             Bugs and feature requests will be tracked at:
419              
420             https://framagit.org/fiat-tux/mojolicious/mojolicious-plugin-emailmailer/issues
421              
422             =head1 AUTHOR
423              
424             Luc DIDRY
425             CPAN ID: LDIDRY
426             ldidry@cpan.org
427             https://fiat-tux.fr/
428              
429             =head1 COPYRIGHT
430              
431             This program is free software; you can redistribute
432             it and/or modify it under the same terms as Perl itself.
433              
434             The full text of the license can be found in the
435             LICENSE file included with this module.
436              
437             =head1 SEE ALSO
438              
439             L, L, L, L, L.
440              
441             =cut