File Coverage

blib/lib/Mojolicious/Plugin/EmailMailer.pm
Criterion Covered Total %
statement 114 128 89.0
branch 13 38 34.2
condition 13 23 56.5
subroutine 21 22 95.4
pod 1 1 100.0
total 162 212 76.4


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

use Perl or die;

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