File Coverage

blib/lib/Net/Async/SMTP/Client.pm
Criterion Covered Total %
statement 18 81 22.2
branch 0 8 0.0
condition 0 5 0.0
subroutine 6 26 23.0
pod 12 12 100.0
total 36 132 27.2


line stmt bran cond sub pod time code
1             package Net::Async::SMTP::Client;
2              
3 1     1   219262 use strict;
  1         3  
  1         42  
4 1     1   6 use warnings;
  1         2  
  1         73  
5 1     1   7 use parent qw(IO::Async::Notifier);
  1         6  
  1         9  
6              
7             our $VERSION = '0.004'; ## VERSION
8             ## AUTHORITY
9              
10             =head1 NAME
11              
12             Net::Async::SMTP::Client - sending email with IO::Async
13              
14             =head1 SYNOPSIS
15              
16             #!/usr/bin/env perl
17             use strict;
18             use warnings;
19             use IO::Async::Loop;
20             use Net::Async::SMTP::Client;
21             use Email::Simple;
22             my $email = Email::Simple->create(
23             header => [
24             From => 'someone@example.com',
25             To => 'other@example.com',
26             Subject => 'NaSMTP test',
27             ],
28             attributes => {
29             encoding => "8bitmime",
30             charset => "UTF-8",
31             },
32             body_str => '... text ...',
33             );
34             my $loop = IO::Async::Loop->new;
35             $loop->add(
36             my $smtp = Net::Async::SMTP::Client->new(
37             domain => 'example.com',
38             )
39             );
40             $smtp->connected->then(sub {
41             $smtp->login(
42             user => '...',
43             pass => '...',
44             )
45             })->then(sub {
46             $smtp->send(
47             to => 'someone@example.com',
48             from => 'other@example.com',
49             data => $email->as_string,
50             )
51             })->get;
52              
53             =head1 DESCRIPTION
54              
55             Provides basic email sending capability for L, using
56             the L implementation.
57              
58             See L for a list of supported features
59             and usage instructions.
60              
61             =cut
62              
63 1     1   25833 use IO::Async::Resolver::DNS;
  1         149140  
  1         168  
64 1     1   24 use Future::Utils qw(try_repeat_until_success);
  1         3  
  1         78  
65              
66 1     1   714 use Net::Async::SMTP::Connection;
  1         6  
  1         1409  
67              
68             =head1 METHODS
69              
70             =head2 connection
71              
72             Establishes or returns the TCP connection to the SMTP server.
73              
74             =over 4
75              
76             =item * If we had a host, we'll connect directly.
77              
78             =item * If we have a domain, then we'll do an MX lookup on it.
79              
80             =item * If we don't have either, you'll probably just see errors
81             or unresolved futures.
82              
83             =back
84              
85             Returns the L representing the connection. Attach events via
86             methods on L such as C, C etc.
87              
88             See also: L
89              
90             =cut
91              
92             sub connection {
93 0     0 1   my $self = shift;
94             (defined($self->host)
95             ? Future->wrap($self->host)
96             : $self->mx_lookup($self->domain))->then(sub {
97 0     0     my @hosts = @_;
98             try_repeat_until_success {
99 0           my $host = shift;
100 0           $self->debug_printf("Trying connection to [%s]", $host);
101             $self->loop->connect(
102             socktype => 'stream',
103             host => $host,
104             service => $self->port || 'smtp',
105             )->on_fail(sub {
106 0           $self->debug_printf("Failed connection to [%s], have %d left to try", $host, scalar @hosts);
107             })
108 0   0       } foreach => \@hosts;
  0            
109 0 0         });
110             }
111              
112             =head2 mx_lookup
113              
114             Looks up MX records for the given domain.
115              
116             Returns a L which will resolve to the list of records found.
117              
118             =cut
119              
120             sub mx_lookup {
121 0     0 1   my $self = shift;
122 0           my $domain = shift;
123 0           my $resolver = $self->loop->resolver;
124              
125             # Wrap the resolver query as a Future
126 0           my $f = $self->loop->new_future;
127             $resolver->res_query(
128             dname => $domain,
129             type => "MX",
130             on_resolved => sub {
131 0     0     $f->done(@_);
132 0           undef $f;
133             },
134             on_error => sub {
135 0     0     $f->fail(@_);
136 0           undef $f;
137             },
138 0           );
139              
140             # ... and return just the list of hosts we want to contact as our result
141             $f->transform(
142             done => sub {
143 0     0     my $pkt = shift;
144 0           my @host;
145 0           foreach my $mx ( $pkt->answer ) {
146 0 0         next unless $mx->type eq "MX";
147 0           push @host, [ $mx->preference, $mx->exchange ];
148             }
149             # sort things - possibly already handled by the resolver
150 0           map $_->[1], sort { $_->[0] <=> $_->[1] } @host;
  0            
151             }
152 0           );
153             }
154              
155             =head2 configure
156              
157             Overrides L C to apply SMTP-specific config.
158              
159             =cut
160              
161             sub configure {
162 0     0 1   my $self = shift;
163 0           my %args = @_;
164 0           for(grep exists $args{$_}, qw(host user pass auth domain)) {
165 0           $self->{$_} = delete $args{$_};
166             }
167             # SSL support
168 0           $self->{$_} = delete $args{$_} for grep /^SSL_/, keys %args;
169 0           $self->SUPER::configure(%args);
170             }
171              
172             =head2 connected
173              
174             Returns the L indicating our SMTP connection.
175              
176             Resolves to a L instance on
177             success.
178              
179             =cut
180              
181             sub connected {
182 0     0 1   my $self = shift;
183             $self->{connected} ||= $self->connection->then(sub {
184 0     0     my $sock = shift;
185 0 0         my $stream = Net::Async::SMTP::Connection->new(
186             handle => $sock,
187             $self->auth
188             ? (auth => $self->auth)
189             : (),
190             );
191 0           $self->add_child($stream);
192             $stream->send_greeting->then(sub {
193 0 0         return Future->wrap($stream) unless $stream->has_feature('STARTTLS');
194              
195             # Currently need to have this loaded to find ->sslwrite
196 0           require IO::Async::SSLStream;
197              
198 0           $stream->starttls(
199             $self->ssl_parameters
200             )
201 0           });
202 0   0       });
203             }
204              
205             =head2 ssl_parameters
206              
207             Returns any defined SSL parameters as passed to the constructor
208             or L.
209              
210             =cut
211              
212             sub ssl_parameters {
213 0     0 1   my $self = shift;
214 0           map { $_, $self->{$_} } grep /^SSL_/, keys %$self;
  0            
215             }
216              
217             =head2 login
218              
219             Attempts login, connecting first if required.
220              
221             Returns a L which will resolve with this instance when the login completes.
222              
223             =cut
224              
225             sub login {
226 0     0 1   my $self = shift;
227 0           my %args = @_;
228             $self->connected->then(sub {
229 0     0     my $connection = shift;
230 0           $connection->login(%args);
231 0           });
232             }
233              
234             =head2 send
235              
236             Attempts to send message(s), connecting first if required.
237              
238             If this server requires login, you'll need to call L yourself.
239              
240             See L.
241              
242             Returns a L.
243              
244             =cut
245              
246             sub send {
247 0     0 1   my $self = shift;
248 0           my %args = @_;
249              
250             $self->connected->then(sub {
251 0     0     my $connection = shift;
252 0           $connection->send(%args);
253             })
254 0           }
255              
256             =head2 quit
257              
258             Quit the SMTP connection, unsetting the connection.
259              
260             Returns a L.
261              
262             =cut
263              
264             sub quit {
265 0     0 1   my $self = shift;
266              
267             $self->connected->then(sub {
268 0     0     my $connection = shift;
269 0           my $rv = $connection->quit;
270 0           undef $self->{connected};
271 0           return $rv;
272             })
273 0           }
274              
275             =head1 METHODS - Accessors
276              
277             =cut
278              
279             =head2 port
280              
281             Returns the port used for communicating with the server,
282             or undef for default (25).
283              
284             =cut
285              
286 0     0 1   sub port { shift->{port} }
287              
288             =head2 host
289              
290             Returns the host we're going to connect to.
291              
292             =cut
293              
294 0     0 1   sub host { shift->{host} }
295              
296             =head2 domain
297              
298             Returns the domain used for the email server.
299              
300             =cut
301              
302 0     0 1   sub domain { shift->{domain} }
303              
304             =head2 auth
305              
306             Returns the auth method used for server authentication.
307              
308             =cut
309              
310 0     0 1   sub auth { shift->{auth} }
311              
312             1;
313              
314             __END__