File Coverage

blib/lib/Email/MIME/Kit.pm
Criterion Covered Total %
statement 57 58 98.2
branch 15 16 93.7
condition 2 2 100.0
subroutine 15 16 93.7
pod 0 3 0.0
total 89 95 93.6


line stmt bran cond sub pod time code
1             package Email::MIME::Kit 3.000007;
2             # ABSTRACT: build messages from templates
3              
4 6     6   441971 use v5.20.0;
  6         116  
5 6     6   3345 use Moose 0.65; # maybe_type
  6         2861501  
  6         51  
6 6     6   47261 use Moose::Util::TypeConstraints;
  6         14  
  6         63  
7              
8 6     6   18646 use Email::MIME 1.930; # header_raw
  6         358141  
  6         247  
9 6     6   62 use Email::MessageID 1.400; # for in_brackets method
  6         98  
  6         118  
10 6     6   34 use Module::Runtime ();
  6         15  
  6         92  
11 6     6   2692 use String::RewritePrefix;
  6         6885  
  6         37  
12              
13             #pod =head1 SYNOPSIS
14             #pod
15             #pod use Email::MIME::Kit;
16             #pod
17             #pod my $kit = Email::MIME::Kit->new({ source => 'mkits/sample.mkit' });
18             #pod
19             #pod my $email = $kit->assemble({
20             #pod account => $new_signup,
21             #pod verification_code => $token,
22             #pod ... and any other template vars ...
23             #pod });
24             #pod
25             #pod $transport->send($email, { ... });
26             #pod
27             #pod =head1 DESCRIPTION
28             #pod
29             #pod Email::MIME::Kit is a templating system for email messages. Instead of trying
30             #pod to be yet another templating system for chunks of text, it makes it easy to
31             #pod build complete email messages.
32             #pod
33             #pod It handles the construction of multipart messages, text and HTML alternatives,
34             #pod attachments, interpart linking, string encoding, and parameter validation.
35             #pod
36             #pod Although nearly every part of Email::MIME::Kit is a replaceable component, the
37             #pod stock configuration is probably enough for most use. A message kit will be
38             #pod stored as a directory that might look like this:
39             #pod
40             #pod sample.mkit/
41             #pod manifest.json
42             #pod body.txt
43             #pod body.html
44             #pod logo.jpg
45             #pod
46             #pod The manifest file tells Email::MIME::Kit how to put it all together, and might
47             #pod look something like this:
48             #pod
49             #pod {
50             #pod "renderer": "TT",
51             #pod "header": [
52             #pod { "From": "WY Corp <noreplies@wy.example.com>" },
53             #pod { "Subject": "Welcome aboard, [% recruit.name %]!" }
54             #pod ],
55             #pod "alternatives": [
56             #pod { "type": "text/plain", "path": "body.txt" },
57             #pod {
58             #pod "type": "text/html",
59             #pod "path": "body.html",
60             #pod "container_type": "multipart/related",
61             #pod "attachments": [ { "type": "image/jpeg", "path": "logo.jpg" } ]
62             #pod }
63             #pod ]
64             #pod }
65             #pod
66             #pod B<Inline images> may be accessed with the function C<cid_for>, for example to include the above logo.jpg:
67             #pod
68             #pod <img style="margin: 0 auto" src="cid:[% cid_for("logo.jpg") %]">
69             #pod
70             #pod B<Please note:> the assembly of HTML documents as multipart/related bodies may
71             #pod be simplified with an alternate assembler in the future.
72             #pod
73             #pod The above manifest would build a multipart alternative message. GUI mail
74             #pod clients would see a rendered HTML document with the logo graphic visible from
75             #pod the attachment. Text mail clients would see the plaintext.
76             #pod
77             #pod Both the HTML and text parts would be rendered using the named renderer, which
78             #pod here is Template-Toolkit.
79             #pod
80             #pod The message would be assembled and returned as an Email::MIME object, just as
81             #pod easily as suggested in the L</SYNOPSIS> above.
82             #pod
83             #pod =head1 ENCODING ISSUES
84             #pod
85             #pod In general, "it should all just work" ... starting in version v3.
86             #pod
87             #pod Email::MIME::Kit assumes that any file read for the purpose of becoming a
88             #pod C<text/*>-type part is encoded in UTF-8. It will decode them and work with
89             #pod their contents as text strings. Renderers will be passed text strings to
90             #pod render, and so on. This, further, means that strings passed to the C<assemble>
91             #pod method for use in rendering should also be text strings.
92             #pod
93             #pod In older versions of Email::MIME::Kit, files read from disk were read in raw
94             #pod mode and then handled as octet strings. Meanwhile, the manifest's contents
95             #pod (and, thus, any templates stored as strings in the manifest) were decoded into
96             #pod text strings. This could lead to serious problems. For example: the
97             #pod F<manifest.json> file might contain:
98             #pod
99             #pod "header": [
100             #pod { "Subject": "Message for [% customer_name %]" },
101             #pod ...
102             #pod ]
103             #pod
104             #pod ...while a template on disk might contain:
105             #pod
106             #pod Dear [% customer_name %],
107             #pod ...
108             #pod
109             #pod If the customer's name isn't ASCII, there was no right way to pass it in. The
110             #pod template on disk would expect UTF-8, but the template in the manifest would
111             #pod expect Unicode text. Users prior to v3 may have taken strange steps to get
112             #pod around this problem, understanding that some templates were treated differently
113             #pod than others. This means that some review of kits is in order when upgrading
114             #pod from earlier versions of Email::MIME::Kit.
115             #pod
116             #pod =cut
117              
118             has source => (is => 'ro', required => 1);
119              
120             has manifest => (reader => 'manifest', writer => '_set_manifest');
121              
122             my @auto_attrs = (
123             [ manifest_reader => ManifestReader => JSON => [ 'read_manifest' ] ],
124             [ kit_reader => KitReader => Dir => [ 'get_kit_entry',
125             'get_decoded_kit_entry' ] ],
126             );
127              
128             for my $tuple (@auto_attrs) {
129             my ($attr, $role, $default, $handles) = @$tuple;
130              
131             my $seed = "_${attr}_seed";
132             my $base_ns = "Email::MIME::Kit::$role";
133             my $role_pkg = "Email::MIME::Kit::Role::$role";
134              
135             has $seed => (
136             is => 'ro',
137             init_arg => $attr,
138             default => "=Email::MIME::Kit::${role}::$default",
139             );
140              
141             has $attr => (
142             reader => $attr,
143             writer => "_set_$attr",
144             isa => role_type($role_pkg),
145             init_arg => undef,
146             lazy => 1,
147             default => sub {
148             my ($self) = @_;
149              
150             my $comp = $self->_build_component($base_ns, $self->$seed);
151              
152             return $comp;
153             },
154             handles => $handles,
155             );
156             }
157              
158             has validator => (
159             is => 'ro',
160             isa => maybe_type(role_type('Email::MIME::Kit::Role::Validator')),
161             lazy => 1, # is this really needed? -- rjbs, 2009-01-20
162             default => sub {
163             my ($self) = @_;
164             return $self->_build_component(
165             'Email::MIME::Kit::Validator',
166             $self->manifest->{validator},
167             );
168             },
169             );
170              
171             sub _build_component {
172 52     52   187 my ($self, $base_namespace, $entry, $extra) = @_;
173              
174 52 100       297 return unless $entry;
175              
176 46         101 my ($class, $arg);
177 46 100       134 if (ref $entry) {
178 5         17 ($class, $arg) = @$entry;
179             } else {
180 41         106 ($class, $arg) = ($entry, {});
181             }
182              
183 46         505 $class = String::RewritePrefix->rewrite(
184             { '=' => '', '' => ($base_namespace . q{::}) },
185             $class,
186             );
187              
188 46         3608 Module::Runtime::require_module($class);
189 46 100       22941 $class->new({ %$arg, %{ $extra || {} }, kit => $self });
  46         1853  
190             }
191              
192             sub BUILD {
193 8     8 0 25 my ($self) = @_;
194              
195 8         61 my $manifest = $self->read_manifest;
196 8         354 $self->_set_manifest($manifest);
197              
198 8 100       53 if ($manifest->{kit_reader}) {
199             my $kit_reader = $self->_build_component(
200             'Email::MIME::Kit::KitReader',
201             $manifest->{kit_reader},
202 1         9 );
203              
204 1         795 $self->_set_kit_reader($kit_reader);
205             }
206              
207 8         39 $self->_setup_default_renderer;
208             }
209              
210             sub _setup_default_renderer {
211 8     8   27 my ($self) = @_;
212             return unless my $renderer = $self->_build_component(
213             'Email::MIME::Kit::Renderer',
214             $self->manifest->{renderer},
215 8 100       236 );
216              
217 5         207 $self->_set_default_renderer($renderer);
218             }
219              
220             sub assemble {
221 11     11 0 9986 my ($self, $stash) = @_;
222              
223 11 100       403 $self->validator->validate($stash) if $self->validator;
224              
225             # Do I really need or want to do this? Anything that alters the stash should
226             # do so via localization. -- rjbs, 2009-01-20
227 9 100       249 my $copied_stash = { %{ $stash || {} } };
  9         60  
228              
229 9         276 my $email = $self->assembler->assemble($copied_stash);
230              
231 9         43635 my $header = $email->header('Message-ID');
232 9 50       474 $email->header_set('Message-ID' => $self->_generate_content_id->in_brackets)
233             unless defined $header;
234              
235 9         6564 return $email;
236             }
237              
238 0     0 0 0 sub kit { $_[0] }
239              
240             sub _assembler_from_manifest {
241 19     19   60 my ($self, $manifest, $parent) = @_;
242              
243             $self->_build_component(
244             'Email::MIME::Kit::Assembler',
245 19   100     188 $manifest->{assembler} || 'Standard',
246             {
247             manifest => $manifest,
248             parent => $parent,
249             },
250             );
251             }
252              
253             has default_renderer => (
254             reader => 'default_renderer',
255             writer => '_set_default_renderer',
256             isa => role_type('Email::MIME::Kit::Role::Renderer'),
257             );
258              
259             has assembler => (
260             reader => 'assembler',
261             isa => role_type('Email::MIME::Kit::Role::Assembler'),
262             required => 1,
263             lazy => 1,
264             default => sub {
265             my ($self) = @_;
266             return $self->_assembler_from_manifest($self->manifest);
267             }
268             );
269              
270             sub _generate_content_id {
271 13     13   77 Email::MessageID->new;
272             }
273              
274             #pod =head1 AUTHOR
275             #pod
276             #pod This code was written in 2009 by Ricardo SIGNES. It was based on a previous
277             #pod implementation by Hans Dieter Pearcey written in 2006.
278             #pod
279             #pod The development of this code was sponsored by Pobox.com. Thanks, Pobox!
280             #pod
281             #pod =cut
282              
283 6     6   6688 no Moose::Util::TypeConstraints;
  6         21  
  6         127  
284 6     6   1917 no Moose;
  6         19  
  6         50  
285             __PACKAGE__->meta->make_immutable;
286             1;
287              
288             __END__
289              
290             =pod
291              
292             =encoding UTF-8
293              
294             =head1 NAME
295              
296             Email::MIME::Kit - build messages from templates
297              
298             =head1 VERSION
299              
300             version 3.000007
301              
302             =head1 SYNOPSIS
303              
304             use Email::MIME::Kit;
305              
306             my $kit = Email::MIME::Kit->new({ source => 'mkits/sample.mkit' });
307              
308             my $email = $kit->assemble({
309             account => $new_signup,
310             verification_code => $token,
311             ... and any other template vars ...
312             });
313              
314             $transport->send($email, { ... });
315              
316             =head1 DESCRIPTION
317              
318             Email::MIME::Kit is a templating system for email messages. Instead of trying
319             to be yet another templating system for chunks of text, it makes it easy to
320             build complete email messages.
321              
322             It handles the construction of multipart messages, text and HTML alternatives,
323             attachments, interpart linking, string encoding, and parameter validation.
324              
325             Although nearly every part of Email::MIME::Kit is a replaceable component, the
326             stock configuration is probably enough for most use. A message kit will be
327             stored as a directory that might look like this:
328              
329             sample.mkit/
330             manifest.json
331             body.txt
332             body.html
333             logo.jpg
334              
335             The manifest file tells Email::MIME::Kit how to put it all together, and might
336             look something like this:
337              
338             {
339             "renderer": "TT",
340             "header": [
341             { "From": "WY Corp <noreplies@wy.example.com>" },
342             { "Subject": "Welcome aboard, [% recruit.name %]!" }
343             ],
344             "alternatives": [
345             { "type": "text/plain", "path": "body.txt" },
346             {
347             "type": "text/html",
348             "path": "body.html",
349             "container_type": "multipart/related",
350             "attachments": [ { "type": "image/jpeg", "path": "logo.jpg" } ]
351             }
352             ]
353             }
354              
355             B<Inline images> may be accessed with the function C<cid_for>, for example to include the above logo.jpg:
356              
357             <img style="margin: 0 auto" src="cid:[% cid_for("logo.jpg") %]">
358              
359             B<Please note:> the assembly of HTML documents as multipart/related bodies may
360             be simplified with an alternate assembler in the future.
361              
362             The above manifest would build a multipart alternative message. GUI mail
363             clients would see a rendered HTML document with the logo graphic visible from
364             the attachment. Text mail clients would see the plaintext.
365              
366             Both the HTML and text parts would be rendered using the named renderer, which
367             here is Template-Toolkit.
368              
369             The message would be assembled and returned as an Email::MIME object, just as
370             easily as suggested in the L</SYNOPSIS> above.
371              
372             =head1 PERL VERSION
373              
374             This library should run on perls released even a long time ago. It should work
375             on any version of perl released in the last five years.
376              
377             Although it may work on older versions of perl, no guarantee is made that the
378             minimum required version will not be increased. The version may be increased
379             for any reason, and there is no promise that patches will be accepted to lower
380             the minimum required perl.
381              
382             =head1 ENCODING ISSUES
383              
384             In general, "it should all just work" ... starting in version v3.
385              
386             Email::MIME::Kit assumes that any file read for the purpose of becoming a
387             C<text/*>-type part is encoded in UTF-8. It will decode them and work with
388             their contents as text strings. Renderers will be passed text strings to
389             render, and so on. This, further, means that strings passed to the C<assemble>
390             method for use in rendering should also be text strings.
391              
392             In older versions of Email::MIME::Kit, files read from disk were read in raw
393             mode and then handled as octet strings. Meanwhile, the manifest's contents
394             (and, thus, any templates stored as strings in the manifest) were decoded into
395             text strings. This could lead to serious problems. For example: the
396             F<manifest.json> file might contain:
397              
398             "header": [
399             { "Subject": "Message for [% customer_name %]" },
400             ...
401             ]
402              
403             ...while a template on disk might contain:
404              
405             Dear [% customer_name %],
406             ...
407              
408             If the customer's name isn't ASCII, there was no right way to pass it in. The
409             template on disk would expect UTF-8, but the template in the manifest would
410             expect Unicode text. Users prior to v3 may have taken strange steps to get
411             around this problem, understanding that some templates were treated differently
412             than others. This means that some review of kits is in order when upgrading
413             from earlier versions of Email::MIME::Kit.
414              
415             =head1 AUTHOR
416              
417             This code was written in 2009 by Ricardo SIGNES. It was based on a previous
418             implementation by Hans Dieter Pearcey written in 2006.
419              
420             The development of this code was sponsored by Pobox.com. Thanks, Pobox!
421              
422             =head1 AUTHOR
423              
424             Ricardo Signes <rjbs@cpan.org>
425              
426             =head1 CONTRIBUTORS
427              
428             =for stopwords Charlie Garrison fREW Schmidt hdp Kaitlyn Parkhurst Ricardo Signes
429              
430             =over 4
431              
432             =item *
433              
434             Charlie Garrison <garrison@zeta.org.au>
435              
436             =item *
437              
438             fREW Schmidt <frioux@gmail.com>
439              
440             =item *
441              
442             hdp <hdp@1bcdbe44-fcfd-0310-b51b-975661d93aa0>
443              
444             =item *
445              
446             Kaitlyn Parkhurst <symkat@symkat.com>
447              
448             =item *
449              
450             Ricardo Signes <cpan@semiotic.systems>
451              
452             =item *
453              
454             Ricardo Signes <rjbs@semiotic.systems>
455              
456             =back
457              
458             =head1 COPYRIGHT AND LICENSE
459              
460             This software is copyright (c) 2023 by Ricardo Signes.
461              
462             This is free software; you can redistribute it and/or modify it under
463             the same terms as the Perl 5 programming language system itself.
464              
465             =cut