| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Email::MIME::Kit::Bulk; | 
| 2 |  |  |  |  |  |  | our $AUTHORITY = 'cpan:YANICK'; | 
| 3 |  |  |  |  |  |  | # ABSTRACT: Email::MIME::Kit-based bulk mailer | 
| 4 |  |  |  |  |  |  | $Email::MIME::Kit::Bulk::VERSION = '0.0.3'; | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 10 |  |  | 10 |  | 41 | use Moose; | 
|  | 10 |  |  |  |  | 19 |  | 
|  | 10 |  |  |  |  | 69 |  | 
| 7 | 10 |  |  | 10 |  | 41603 | use namespace::autoclean; | 
|  | 10 |  |  |  |  | 11 |  | 
|  | 10 |  |  |  |  | 81 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 10 |  |  | 10 |  | 5745 | use Email::MIME; | 
|  | 10 |  |  |  |  | 197567 |  | 
|  | 10 |  |  |  |  | 299 |  | 
| 10 | 10 |  |  | 10 |  | 4205 | use Email::MIME::Kit; | 
|  | 10 |  |  |  |  | 506688 |  | 
|  | 10 |  |  |  |  | 450 |  | 
| 11 | 10 |  |  | 10 |  | 5613 | use Email::Sender::Simple 'sendmail'; | 
|  | 10 |  |  |  |  | 930022 |  | 
|  | 10 |  |  |  |  | 91 |  | 
| 12 | 10 |  |  | 10 |  | 7767 | use MooseX::Types::Email; | 
|  | 10 |  |  |  |  | 1031527 |  | 
|  | 10 |  |  |  |  | 322 |  | 
| 13 | 10 |  |  | 10 |  | 16933 | use MooseX::Types::Path::Tiny qw/ Path /; | 
|  | 10 |  |  |  |  | 1070612 |  | 
|  | 10 |  |  |  |  | 71 |  | 
| 14 | 10 |  |  | 10 |  | 18918 | use Try::Tiny; | 
|  | 10 |  |  |  |  | 10 |  | 
|  | 10 |  |  |  |  | 831 |  | 
| 15 | 10 |  |  | 10 |  | 5958 | use PerlX::Maybe; | 
|  | 10 |  |  |  |  | 15278 |  | 
|  | 10 |  |  |  |  | 632 |  | 
| 16 | 10 |  |  | 10 |  | 4718 | use List::AllUtils qw/ sum0 /; | 
|  | 10 |  |  |  |  | 73680 |  | 
|  | 10 |  |  |  |  | 696 |  | 
| 17 | 10 |  |  | 10 |  | 4522 | use MCE::Map; | 
|  | 10 |  |  |  |  | 323665 |  | 
|  | 10 |  |  |  |  | 69 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 10 |  |  | 10 |  | 6790 | use Email::MIME::Kit::Bulk::Kit; | 
|  | 10 |  |  |  |  | 199 |  | 
|  | 10 |  |  |  |  | 326 |  | 
| 20 | 10 |  |  | 10 |  | 5280 | use Email::MIME::Kit::Bulk::Target; | 
|  | 10 |  |  |  |  | 31 |  | 
|  | 10 |  |  |  |  | 1044 |  | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | has targets => ( | 
| 24 |  |  |  |  |  |  | traits   => ['Array'], | 
| 25 |  |  |  |  |  |  | isa      => 'ArrayRef[Email::MIME::Kit::Bulk::Target]', | 
| 26 |  |  |  |  |  |  | required => 1, | 
| 27 |  |  |  |  |  |  | handles  => { | 
| 28 |  |  |  |  |  |  | targets     => 'elements', | 
| 29 |  |  |  |  |  |  | num_targets => 'count', | 
| 30 |  |  |  |  |  |  | }, | 
| 31 |  |  |  |  |  |  | ); | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | has kit => ( | 
| 35 |  |  |  |  |  |  | is       => 'ro', | 
| 36 |  |  |  |  |  |  | isa      => Path, | 
| 37 |  |  |  |  |  |  | required => 1, | 
| 38 |  |  |  |  |  |  | coerce   => 1, | 
| 39 |  |  |  |  |  |  | ); | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | has from => ( | 
| 43 |  |  |  |  |  |  | is       => 'ro', | 
| 44 |  |  |  |  |  |  | isa      => 'MooseX::Types::Email::EmailAddress', | 
| 45 |  |  |  |  |  |  | required => 1, | 
| 46 |  |  |  |  |  |  | ); | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | has processes => ( | 
| 50 |  |  |  |  |  |  | is        => 'ro', | 
| 51 |  |  |  |  |  |  | isa       => 'Maybe[Int]', | 
| 52 |  |  |  |  |  |  | predicate => 'has_processes', | 
| 53 |  |  |  |  |  |  | ); | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | sub single_process { | 
| 56 | 10 |  |  | 10 |  | 65 | no warnings; | 
|  | 10 |  |  |  |  | 11 |  | 
|  | 10 |  |  |  |  | 8409 |  | 
| 57 | 9 |  |  | 9 | 0 | 225 | return $_[0]->processes == 1; | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | has verbose => ( | 
| 61 |  |  |  |  |  |  | isa => 'Bool', | 
| 62 |  |  |  |  |  |  | is => 'ro', | 
| 63 |  |  |  |  |  |  | default => 0, | 
| 64 |  |  |  |  |  |  | ); | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | has transport => ( | 
| 67 |  |  |  |  |  |  | is => 'ro', | 
| 68 |  |  |  |  |  |  | ); | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | sub mime_kit { | 
| 71 | 5 |  |  | 5 | 0 | 14 | my $self = shift; | 
| 72 | 5 |  |  |  |  | 176 | Email::MIME::Kit::Bulk::Kit->new({ | 
| 73 |  |  |  |  |  |  | source => $self->kit->stringify, | 
| 74 |  |  |  |  |  |  | @_, | 
| 75 |  |  |  |  |  |  | }); | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | around BUILDARGS => sub { | 
| 79 |  |  |  |  |  |  | my $orig  = shift; | 
| 80 |  |  |  |  |  |  | my $class = shift; | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | my $params = $class->$orig(@_); | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | if (!exists $params->{targets} && exists $params->{to}) { | 
| 85 |  |  |  |  |  |  | $params->{targets} = [ | 
| 86 |  |  |  |  |  |  | Email::MIME::Kit::Bulk::Target->new( | 
| 87 |  |  |  |  |  |  | to => delete $params->{to}, | 
| 88 |  |  |  |  |  |  | map { maybe $_ => delete $params->{$_} } qw/ cc bcc / | 
| 89 |  |  |  |  |  |  | ) | 
| 90 |  |  |  |  |  |  | ]; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | return $params; | 
| 94 |  |  |  |  |  |  | }; | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | sub send { | 
| 98 | 9 |  |  | 9 | 1 | 9 | my $self = shift; | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 9 |  |  |  |  | 117 | my $af = STDOUT->autoflush; | 
| 101 |  |  |  |  |  |  |  | 
| 102 | 9 | 50 |  |  |  | 711 | MCE::Map::init { max_workers => $self->processes } | 
| 103 |  |  |  |  |  |  | if $self->has_processes; | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | my $errors = sum0 | 
| 106 |  |  |  |  |  |  | $self->single_process | 
| 107 | 0 |  |  |  |  | 0 | ? map     { $self->send_target($_) } $self->targets | 
| 108 | 9 | 50 |  | 2 |  | 36 | : mce_map { $self->send_target($_) } $self->targets; | 
|  | 2 |  |  |  |  | 14531 |  | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 1 | 0 |  |  |  | 263749 | warn "\n" . ($self->num_targets - $errors) . ' email(s) sent successfully' | 
|  |  | 50 |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | . ($errors ? " ($errors failure(s))" : '') . "\n" if $self->verbose; | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 1 |  |  |  |  | 28 | STDOUT->autoflush($af); | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 1 |  |  |  |  | 133 | return $self->num_targets - $errors; | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | sub send_target { | 
| 119 | 2 |  |  | 2 | 0 | 12 | my( $self, $target ) = @_; | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 2 |  |  |  |  | 16 | my $email = $self->assemble_mime_kit($target); | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | # work around bugs in q-p encoding (it forces \r\n, but the sendmail | 
| 124 |  |  |  |  |  |  | # executable expects \n, or something like that) | 
| 125 | 2 |  |  |  |  | 397 | (my $text = $email->as_string) =~ s/\x0d\x0a/\n/g; | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | return try { | 
| 128 | 2 |  |  | 2 |  | 112 | sendmail( | 
| 129 |  |  |  |  |  |  | $text, | 
| 130 |  |  |  |  |  |  | { | 
| 131 |  |  |  |  |  |  | from => $target->from, | 
| 132 |  |  |  |  |  |  | to   => [ $target->recipients ], | 
| 133 |  |  |  |  |  |  | maybe transport => $self->transport, | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  | ); | 
| 136 | 2 | 50 |  |  |  | 79034 | print '.' if $self->verbose; | 
| 137 | 2 |  |  |  |  | 5 | 0; | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  | catch { | 
| 140 | 0 | 0 | 0 | 0 |  | 0 | my @recipients = (blessed($_) && $_->isa('Email::Sender::Failure')) | 
| 141 |  |  |  |  |  |  | ? ($_->recipients) | 
| 142 |  |  |  |  |  |  | : ($target->recipients); | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | # XXX better error handling here - logging? | 
| 145 | 0 |  |  |  |  | 0 | warn 'Failed to send to ' . join(', ', @recipients) . ': ' | 
| 146 |  |  |  |  |  |  | . "$_"; | 
| 147 |  |  |  |  |  |  |  | 
| 148 | 0 | 0 |  |  |  | 0 | print 'x' if $self->verbose; | 
| 149 | 0 |  |  |  |  | 0 | 1; | 
| 150 | 2 |  |  |  |  | 228 | }; | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | sub assemble_mime_kit { | 
| 154 | 5 |  |  | 5 | 0 | 24 | my $self = shift; | 
| 155 | 5 |  |  |  |  | 12 | my ($target) = @_; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 5 |  | 66 |  |  | 321 | my $from = $target->from || $self->from; | 
| 158 | 5 |  |  |  |  | 146 | my $to   = $target->to; | 
| 159 | 5 |  |  |  |  | 185 | my @cc   = $target->cc; | 
| 160 |  |  |  |  |  |  |  | 
| 161 | 5 |  |  |  |  | 10 | my %opts; | 
| 162 | 5 | 50 |  |  |  | 193 | $opts{language} = $target->language | 
| 163 |  |  |  |  |  |  | if $target->has_language; | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 5 |  |  |  |  | 34 | my $kit = $self->mime_kit(%opts); | 
| 166 | 5 |  |  |  |  | 151 | my $email = $kit->assemble($target->template_params); | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 5 | 50 |  |  |  | 223180 | if (my @attachments = $target->extra_attachments) { | 
| 169 | 0 |  |  |  |  | 0 | my $old_email = $email; | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | my @parts = map { | 
| 172 | 0 | 0 |  |  |  | 0 | my $attach = ref($_) ? $_ : [$_]; | 
|  | 0 |  |  |  |  | 0 |  | 
| 173 |  |  |  |  |  |  | Email::MIME->create( | 
| 174 |  |  |  |  |  |  | attributes => { | 
| 175 |  |  |  |  |  |  | filename     => $attach->[0], | 
| 176 |  |  |  |  |  |  | name         => $attach->[0], | 
| 177 |  |  |  |  |  |  | encoding     => 'base64', | 
| 178 |  |  |  |  |  |  | disposition  => 'attachment', | 
| 179 |  |  |  |  |  |  | ($attach->[1] | 
| 180 |  |  |  |  |  |  | ? (content_type => $attach->[1]) | 
| 181 |  |  |  |  |  |  | : ()), | 
| 182 |  |  |  |  |  |  | }, | 
| 183 | 0 | 0 |  |  |  | 0 | body => ${ $kit->get_kit_entry($attach->[0]) }, | 
|  | 0 |  |  |  |  | 0 |  | 
| 184 |  |  |  |  |  |  | ); | 
| 185 |  |  |  |  |  |  | } @attachments; | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 0 |  |  |  |  | 0 | $email = Email::MIME->create( | 
| 188 |  |  |  |  |  |  | header => [ | 
| 189 |  |  |  |  |  |  | Subject => $old_email->header_obj->header_raw('Subject'), | 
| 190 |  |  |  |  |  |  | ], | 
| 191 |  |  |  |  |  |  | parts => [ | 
| 192 |  |  |  |  |  |  | $old_email, | 
| 193 |  |  |  |  |  |  | @parts, | 
| 194 |  |  |  |  |  |  | ], | 
| 195 |  |  |  |  |  |  | ); | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | # XXX Email::MIME::Kit reads the manifest.json file as latin1 | 
| 199 |  |  |  |  |  |  | # fix this in a better way once that is fixed? | 
| 200 | 5 |  |  |  |  | 14 | my $subject = $email->header('Subject'); | 
| 201 | 5 |  |  |  |  | 137 | utf8::decode($subject); | 
| 202 | 5 |  |  |  |  | 18 | $email->header_str_set('Subject' => $subject); | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 5 | 50 |  |  |  | 229 | $email->header_str_set('From' => $from) | 
| 205 |  |  |  |  |  |  | unless $email->header('From'); | 
| 206 | 5 | 50 |  |  |  | 361 | $email->header_str_set('To' => $to) | 
| 207 |  |  |  |  |  |  | unless $email->header('To'); | 
| 208 | 5 | 100 | 66 |  |  | 356 | $email->header_str_set('Cc' => join(', ', @cc)) | 
| 209 |  |  |  |  |  |  | unless $email->header('Cc') || !@cc; | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 5 |  |  |  |  | 332 | $email->header_str_set( | 
| 212 |  |  |  |  |  |  | 'X-UserAgent' | 
| 213 |  |  |  |  |  |  | => "Email::MIME::Kit::Bulk v$Email::MIME::Kit::Bulk::VERSION" | 
| 214 |  |  |  |  |  |  | ); | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 5 |  |  |  |  | 406 | return $email; | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | 1; | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | __END__ | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | =pod | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =encoding UTF-8 | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | =head1 NAME | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | Email::MIME::Kit::Bulk - Email::MIME::Kit-based bulk mailer | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | =head1 VERSION | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | version 0.0.3 | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | use Email::MIME::Kit::Bulk; | 
| 240 |  |  |  |  |  |  | use Email::MIME::Kit::Bulk::Target; | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | my @targets = ( | 
| 243 |  |  |  |  |  |  | Email::MIME::Kit::Bulk::Target->new( | 
| 244 |  |  |  |  |  |  | to => 'someone@somewhere.com', | 
| 245 |  |  |  |  |  |  | ), | 
| 246 |  |  |  |  |  |  | Email::MIME::Kit::Bulk::Target->new( | 
| 247 |  |  |  |  |  |  | to => 'someone.else@somewhere.com', | 
| 248 |  |  |  |  |  |  | cc => 'copied@somewhere.com', | 
| 249 |  |  |  |  |  |  | language => 'en', | 
| 250 |  |  |  |  |  |  | ), | 
| 251 |  |  |  |  |  |  | ); | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | my $bulk = Email::MIME::Kit::Bulk->new( | 
| 254 |  |  |  |  |  |  | kit => '/path/to/mime/kit', | 
| 255 |  |  |  |  |  |  | processes => 5, | 
| 256 |  |  |  |  |  |  | targets => \@targets, | 
| 257 |  |  |  |  |  |  | ); | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | $bulk->send; | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | C<Email::MIME::Kit::Bulk> is an extension of L<Email::MIME::Kit> for sending | 
| 264 |  |  |  |  |  |  | bulk emails. The module can be used directly, or via the | 
| 265 |  |  |  |  |  |  | companion script C<emk_bulk>. | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | If a language is specified for a target, C<Email::MIME::Kit> will use | 
| 268 |  |  |  |  |  |  | C<manifest.I<language>.json> to generate its associated email. If no language | 
| 269 |  |  |  |  |  |  | is given, the regular C<manifest.json> will be used instead. | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | If C<emk_bulk> is used, it'll look in the I<kit> directory for a | 
| 272 |  |  |  |  |  |  | C<targets.json> file, which it'll use to create the email targets. | 
| 273 |  |  |  |  |  |  | The format of the C<targets.json> file is a simple serialization of | 
| 274 |  |  |  |  |  |  | the L<Email::MIME::Kit::Bulk::Target> constructor arguments: | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | [ | 
| 277 |  |  |  |  |  |  | { | 
| 278 |  |  |  |  |  |  | "to" : "someone@somewhere.com" | 
| 279 |  |  |  |  |  |  | "cc" : [ | 
| 280 |  |  |  |  |  |  | "someone+cc@somewhere.com" | 
| 281 |  |  |  |  |  |  | ], | 
| 282 |  |  |  |  |  |  | "language" : "en", | 
| 283 |  |  |  |  |  |  | "template_params" : { | 
| 284 |  |  |  |  |  |  | "superlative" : "Fantastic" | 
| 285 |  |  |  |  |  |  | }, | 
| 286 |  |  |  |  |  |  | }, | 
| 287 |  |  |  |  |  |  | { | 
| 288 |  |  |  |  |  |  | "to" : "someone+french@somewhere.com" | 
| 289 |  |  |  |  |  |  | "cc" : [ | 
| 290 |  |  |  |  |  |  | "someone+frenchcc@somewhere.com" | 
| 291 |  |  |  |  |  |  | ], | 
| 292 |  |  |  |  |  |  | "language" : "fr", | 
| 293 |  |  |  |  |  |  | "template_params" : { | 
| 294 |  |  |  |  |  |  | "superlative" : "Extraordinaire" | 
| 295 |  |  |  |  |  |  | }, | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  | ] | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | C<Email::MIME::Kit::Bulk> uses L<MCE> to parallize the sending of the emails. | 
| 300 |  |  |  |  |  |  | The number of processes used can be set via the C<processes> constructor | 
| 301 |  |  |  |  |  |  | argument.  By default L<MCE> will select the number of processes based on | 
| 302 |  |  |  |  |  |  | the number of available | 
| 303 |  |  |  |  |  |  | processors. If the number of processes is set to be C<1>, L<MCE> is bypassed | 
| 304 |  |  |  |  |  |  | altogether. | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | =head1 METHODS | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | =head2 new( %args ) | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | Constructor. | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | =head3 Arguments | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | =over | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | =item targets => \@targets | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | Takes in an array of L<Email::MIME::Kit::Bulk::Target> objects, | 
| 319 |  |  |  |  |  |  | which are the email would-be recipients. | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | Either the argument C<targets> or C<to> must be passed to the constructor. | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | =item to => $email_address | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | Email address of the 'C<To:>' recipient. Ignored if C<targets> is given as well. | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | =item cc => $email_address | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | Email address of the 'C<Cc:>' recipient. Ignored if C<targets> is given as well. | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | =item bcc => $email_address | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | Email address of the 'C<Bcc:>' recipient. Ignored if C<targets> is given as well. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | =item kit => $path | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | Path of the directory holding the files used by L<Email::MIME::Kit>. | 
| 338 |  |  |  |  |  |  | Can be a string or a L<Path::Tiny> object. | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | =item from => $email_address | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | 'C<From>' address for the email . | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | =item processes => $nbr | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | Maximal number of parallel processes used to send the emails. | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | If not specified, will be chosen by L<MCE>. | 
| 349 |  |  |  |  |  |  | If set to 1, the parallel processing will be skipped | 
| 350 |  |  |  |  |  |  | altogether. | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | Not specified by default. | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =back | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | =head2 send() | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | Send the emails. | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =head1 AUTHORS | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | =over 4 | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | =item * | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | Jesse Luehrs    <doy@cpan.org> | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =item * | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | Yanick Champoux <yanick.champoux@iinteractive.com> | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | =back | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | This software is copyright (c) 2017 by Infinity Interactive <contact@iinteractive.com>. | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 379 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | =cut |