File Coverage

blib/lib/Mojolicious/Plugin/SendEmail.pm
Criterion Covered Total %
statement 70 83 84.3
branch 22 32 68.7
condition 12 20 60.0
subroutine 11 12 91.6
pod 1 1 100.0
total 116 148 78.3


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::SendEmail 0.02;
2 5     5   4996752 use v5.26;
  5         25  
3 5     5   38 use warnings;
  5         11  
  5         454  
4              
5             # ABSTRACT: Easily send emails from Mojolicious applications
6              
7             =encoding utf-8
8              
9             =head1 NAME
10              
11             Mojolicious::Plugin::SendEmail - Easily send emails from Mojolicious applications
12              
13             Inspired by both L and L
14              
15             =head1 SYNOPSIS
16              
17             # Register plugin
18             $self->plugin('SendEmail' => {
19             from => '"My Application" ',
20             host => ...,
21             port => ...,
22             recipient_resolver => sub { ... },
23             ...
24             });
25              
26             ...
27              
28             # Send simple email
29             $c->send_email(
30             to => 'mark@tyrrminal.dev',
31             subject => "Alert: MyApp failure",
32             body => "An error occurred when processing nightly data"
33             );
34              
35             ...
36              
37             # Send template-based email with attachments
38             $c->send_email(
39             to => 'daily',
40             subject => 'Nightly Attendance Report',
41             template => 'reports/nightly_attendance',
42             params => {
43             data => $report_data
44             },
45             files => ['generated/Nightly_Attendance_Report.xlsx']
46             );
47              
48             =head1 DESCRIPTION
49              
50             Mojolicious::Plugin::SendEmail is a convenient wrapper for L and
51             L for sending email from Mojolicious commands
52             and controllers, etc.
53              
54             The basic concept here is that it sets up the SMTP details at initial plugin load
55             and then every subsequent call to L or L uses those
56             values, so that subsequently you don't need to worry about them.
57              
58             A little bit of "added value" functionality exists in this module as well:
59              
60             =over
61              
62             =item * Unlike L you I don't need to tell it whether
63             your body is text or HTML. See L below for details
64              
65             =item * This module uses the concept of recipient resolution to support "faux
66             distribution lists". See L below for details
67              
68             =back
69              
70             =cut
71              
72 5     5   807 use Mojo::Base 'Mojolicious::Plugin';
  5         13300  
  5         45  
73              
74 5     5   6449 use Email::Stuffer;
  5         851071  
  5         272  
75 5     5   47 use Params::Util qw(_INSTANCEDOES);
  5         15  
  5         352  
76 5     5   2904 use Email::Sender::Transport::SMTP;
  5         272008  
  5         326  
77              
78 5     5   1297 use experimental qw(signatures);
  5         9259  
  5         3865  
79              
80             =pod
81              
82             =head1 METHODS
83              
84             L inherits all methods from L
85             and implements the following new ones
86              
87             =head2 register
88              
89             Register plugin in L application. All parameters given are passed
90             on to L except for:
91              
92             =head4 from
93              
94             Sets the default C address for all emails. If not given,
95             L<"from"|/from1> will be required in all calls to L and L
96              
97             =head4 recipient_resolver
98              
99             Sets the function that will be used for resolving recipient aliases
100              
101             =cut
102              
103 7     7 1 2350 sub register($self, $app, $conf = {}) {
  7         18  
  7         14  
  7         42  
  7         17  
104 7         51 my $from = delete($conf->{from});
105 7   66 12   83 my $rr = delete($conf->{recipient_resolver}) // sub($add) {$add};
  12         159  
  12         38  
  12         28  
  12         17  
106 7 100       33 delete($conf->{sasl_username}) unless (defined($conf->{sasl_username}));
107 7 50       26 delete($conf->{sasl_password}) unless (defined($conf->{sasl_password}));
108              
109 7         157 my $transport = Email::Sender::Transport::SMTP->new($conf);
110              
111             =pod
112              
113             =head2 send_email
114              
115             Construct and send an email based on configuration and the following parameters:
116              
117             =head4 from
118              
119             Overrides configured from address (if present) for the specific email. If from
120             address was not configured at plugin registration time, this parameter is required.
121              
122             =head4 subject
123              
124             Email message subject. Defaults to empty string if not given.
125              
126             =head4 to
127              
128             =head4 cc
129              
130             =head4 bcc
131              
132             Recipient address parameters for `to`, `cc`, and `bcc`, respectively.
133             C resolves all such recipients through the
134             L function. Takes a single argument, which can be either
135             a string or an ArrayRef (which can contain other arrayrefs if the recipient
136             resolver function handles recursive resolution).
137              
138             =head4 body
139              
140             The content of the email body. Can be plain text or HTML (see L for
141             details) Ignored if L is given.
142              
143             =head4 template
144              
145             The name of a Mojolicious template to use for the email message body. L
146             is ignored if C is specified.
154              
155             =head4 html
156              
157             A boolean flag to manually set message body content type: 1 for HTML, 0 for
158             plain text. If not given, content type will be inferred automatically based on
159             the absence or presence of the string "
160              
161             =head4 attachments
162              
163             An ArrayRef of email attachments whose data is stored in memory. Each item may
164             either be a string, or an ArrayRef of the form [$data, $attributes] where C<$data>
165             is the attachment contents and C<$attributes> is a HashRef of additonal headers
166              
167             See L for details.
168              
169             =head4 files
170              
171             An ArrayRef of email attachments whose data is stored on disk. Each item may
172             either be a filename, or an ArrayRef of the form [$filename, $attributes] where
173             C<$filename> is the relative or absolute path of the file to be attached and
174             C<$attributes> is a HashRef of additonal headers
175              
176             See L for details.
177              
178             =cut
179              
180 18         60 $app->helper(
181 18     18   47355 create_email => sub ($c, %args) {
  18         71  
  18         30  
182 18   100     116 my $mail_from = $args{from} // $from;
183 18 100       67 die("Can't send email: no from address given") unless ($mail_from);
184 17 50       65 die("Can't send email: no to address given") unless ($args{to});
185             my $mail = Email::Stuffer->new(
186             {
187             transport => $transport, # from config
188             from => $mail_from, # from config, overridable, required
189             to => $rr->($args{to}), # required
190 17   100     71 subject => $args{subject} // '', # optional, default empty string
191             }
192             );
193 17 50       41530 my $unarray = sub ($x) {ref($x) eq 'ARRAY' ? $x->@* : $x};
  2         16  
194 17 100       86 $mail->cc($unarray->($rr->($args{cc}))) if ($args{cc});
195 17 50       882 $mail->bcc($unarray->($rr->($args{bcc}))) if ($args{bcc});
196              
197 17         61 my $body = '';
198 17 100       67 if ($args{template}) {
    100          
199             my $bs = $c->render_to_string(
200             format => 'mail',
201             template => $args{template},
202 3   100     37 ($args{params} // {})->%*
203             );
204 3 50       19864 $body = $bs->to_string if ($bs);
205             } elsif ($args{body}) {
206 4         11 $body = $args{body};
207             }
208 17 100       124 $args{html} = index($body, '
209 17 100       42 if ($args{html}) {
210 4         21 $mail->html_body($body);
211             } else {
212 13         51 $mail->text_body($body);
213             }
214              
215 17   50     32171 foreach my $header (($args{headers} // [])->@*) {
216 0         0 $mail->header($header->%*);
217             }
218              
219 17   50     89 foreach my $att (($args{attachments} // [])->@*) {
220 0 0       0 if (ref($att) eq 'ARRAY') {
221 0   0     0 $mail->attach($att->[0], ($att->[1] // {})->%*);
222             } else {
223 0         0 $mail->attach($att);
224             }
225             }
226              
227 17   50     71 foreach my $att (($args{files} // [])->@*) {
228 0 0       0 if (ref($att) eq 'ARRAY') {
229 0   0     0 $mail->attach_file($att->[0], ($att->[1] // {})->%*);
230             } else {
231 0         0 $mail->attach_file($att);
232             }
233             }
234 17         308 return $mail;
235             }
236 6         31689 );
237              
238             =pod
239              
240             =head2 create_email
241              
242             Just like L except that instead of sending the email and returning
243             the result, the unsent L instance is returned instead, facilitating
244             further customization, serialization, delayed sending, etc.
245              
246             =cut
247              
248 0         0 $app->helper(
249 0     0   0 send_email => sub($c, %args) {
  0         0  
  0         0  
250 0         0 $c->create_email(%args)->send();
251             }
252 6         916 );
253              
254             =head2 email_transport( [$transport] )
255              
256             If C<$transport> is provided, sets the email transport used for all future mail
257             sending. Must be a L. Returns the transport object.
258              
259             =cut
260              
261 3         26 $app->helper(
262 3     3   11219 email_transport => sub ($c, $t = undef) {
  3         8  
  3         8  
263 3 100       13 if (defined($t)) {
264 1 50       15 if (_INSTANCEDOES($t, 'Email::Sender::Transport')) {
265 0         0 $transport = $t;
266             } else {
267 1         20 die("email_transport argument must be an Email::Sender::Transport instance");
268             }
269             }
270 2         55 return $transport;
271             }
272 6         634 );
273              
274             }
275              
276             =head1 AUTHOR
277              
278             Mark Tyrrell C<< >>
279              
280             =head1 LICENSE
281              
282             Copyright (c) 2024 Mark Tyrrell
283              
284             Permission is hereby granted, free of charge, to any person obtaining a copy
285             of this software and associated documentation files (the "Software"), to deal
286             in the Software without restriction, including without limitation the rights
287             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
288             copies of the Software, and to permit persons to whom the Software is
289             furnished to do so, subject to the following conditions:
290              
291             The above copyright notice and this permission notice shall be included in all
292             copies or substantial portions of the Software.
293              
294             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
295             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
296             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
297             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
298             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
299             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
300             SOFTWARE.
301              
302             =cut
303              
304             1;
305              
306             __END__