Mojo-SMTP-Client

 view release on metacpan or  search on metacpan

lib/Mojo/SMTP/Client.pm  view on Meta::CPAN

	
	my ($cmd, $arg) = splice @{ $self->{cmds} }, 0, 2;
	unless ($cmd) {
		# no more commands
		if ($self->{stream}) {
			$self->{stream}->timeout(0);
			$self->{stream}->stop;
		}
		return $self->{finally};
	}
	
	if ( my $sub = $self->can("_cmd_$cmd") ) {
		return (
			$self->$sub($arg), sub {
				my ($delay, $resp) = @_;
				
				$delay->pass($resp);
				$delay->steps( $self->_make_cmd_steps() );
			}
		);
	}
	
	croak 'unrecognized command: ', $cmd;
}

# EHLO/HELO
sub _cmd_hello {
	my ($self, $arg) = @_;
	weaken $self;
	
	return (
		sub {
			my $delay = shift;
			$self->_write_cmd('EHLO ' . $arg, CMD_EHLO);
			$self->_read_response($delay->begin);
			$self->{expected_code} = CMD_OK;
		}, 
		sub {
			eval { $self->{resp_checker}->(@_); $_[1]->{checked} = 1 };
			if (my $e = $@) {
				die $e unless $e->isa('Mojo::SMTP::Client::Response');
				my $delay = shift;
				
				$self->_write_cmd('HELO ' . $arg, CMD_HELO);
				$self->_read_response($delay->begin);
			}
		},
		sub {
			my ($delay, $resp) = @_;
			return $delay->pass($resp) if delete $resp->{checked};
			$self->{resp_checker}->($delay, $resp);
		}
	);
}

# STARTTLS
sub _cmd_starttls {
	my ($self, $arg) = @_;
	weaken $self;
	
	require IO::Socket::SSL and IO::Socket::SSL->VERSION(0.98);
	
	return (
		sub {
			my $delay = shift;
			$self->_write_cmd('STARTTLS', CMD_STARTTLS);
			$self->_read_response($delay->begin);
			$self->{expected_code} = CMD_OK;
		},
		$self->{resp_checker},
		sub {
			my ($delay, $resp) = @_;
			$self->{stream}->stop;
			$self->{stream}->timeout(0);
			
			my ($tls_cb, $tid, $loop, $sock);
			
			my $error_handler = sub {
				$loop->remove($tid);
				$loop->reactor->remove($sock);
				$sock = undef;
				$tls_cb->($delay, undef, @_>=2 ? $_[1] : 'Inactivity timeout');
				$tls_cb = $delay = undef;
			};
			
			$sock = IO::Socket::SSL->start_SSL(
				$self->{stream}->steal_handle,
				SSL_ca_file         => $self->tls_ca,
				SSL_cert_file       => $self->tls_cert,
				SSL_key_file        => $self->tls_key,
				SSL_verify_mode     => $self->tls_verify,
				SSL_verifycn_name   => $self->address,
				SSL_verifycn_scheme => $self->tls_ca ? 'smtp' : undef,
				SSL_startHandshake  => 0,
				SSL_error_trap      => $error_handler
			)
			or return $delay->pass(0, $IO::Socket::SSL::SSL_ERROR);
			
			$tls_cb = $delay->begin;
			$loop = $self->_ioloop;
			
			$tid = $loop->timer($self->inactivity_timeout => $error_handler);
			
			$loop->reactor->io($sock => sub {
				if ($sock->connect_SSL) {
					$loop->remove($tid);
					$loop->reactor->remove($sock);
					$self->_make_stream($sock, $loop);
					$self->{starttls} = 1;
					$sock = $loop = undef;
					$tls_cb->($delay, $resp);
					$tls_cb = $delay = undef;
					return;
				}
				
				return $loop->reactor->watch($sock, 1, 0)
					if $IO::Socket::SSL::SSL_ERROR == IO::Socket::SSL::SSL_WANT_READ();
				return $loop->reactor->watch($sock, 0, 1)
					if $IO::Socket::SSL::SSL_ERROR == IO::Socket::SSL::SSL_WANT_WRITE();
				
			})->watch($sock, 0, 1);
		},
		sub {
			my ($delay, $resp, $error) = @_;
			unless ($resp) {
				$self->_rm_stream();
				Mojo::SMTP::Client::Exception::Stream->throw($error);
			}
			
			$delay->pass($resp);
		}
	);
}

# AUTH
sub _cmd_auth {
	my ($self, $arg) = @_;
	weaken $self;
	
	my $type = lc($arg->{type} // 'plain');
	
	my $set_auth_ok = sub {
		my ($delay, $resp) = @_;
		$self->{authorized} = 1;
		$delay->pass($resp);
	};
	
	if ($type eq 'plain') {
		return (
			sub {
				my $delay = shift;
				$self->_write_cmd('AUTH PLAIN '.b64_encode(join("\0", '', $arg->{login}, $arg->{password}), ''), CMD_AUTH);
				$self->_read_response($delay->begin);
				$self->{expected_code} = CMD_OK;
			},
			$self->{resp_checker},
			$set_auth_ok
		);
	}
	
	if ($type eq 'login') {
		return (
			# start auth
			sub {
				my $delay = shift;
				$self->_write_cmd('AUTH LOGIN', CMD_AUTH);
				$self->_read_response($delay->begin);
				$self->{expected_code} = CMD_MORE;
			},
			$self->{resp_checker},
			# send username
			sub {
				my $delay = shift;
				$self->_write_cmd(b64_encode($arg->{login}, ''), CMD_AUTH);
				$self->_read_response($delay->begin);
				$self->{expected_code} = CMD_MORE;
			},
			$self->{resp_checker},
			# send password

lib/Mojo/SMTP/Client.pm  view on Meta::CPAN

			my ($smtp, $resp) = @_;
			warn $resp->error ? 'Failed to send: '.$resp->error : 'Sent successfully';
			Mojo::IOLoop->stop;
		}
	);
	
	Mojo::IOLoop->start;

=back

=head1 DESCRIPTION

With C<Mojo::SMTP::Client> you can easily send emails from your Mojolicious application without
blocking of C<Mojo::IOLoop>.

=head1 EVENTS

C<Mojo::SMTP::Client> inherits all events from L<Mojo::EventEmitter> and can emit the following new ones

=head2 start

	$smtp->on(start => sub {
		my ($smtp) = @_;
		# some servers delays first response to prevent SPAM
		$smtp->inactivity_timeout(5*60);
	});

Emitted whenever a new connection is about to start. You can interrupt sending by dying or throwing an exception
from this callback, C<error> attribute of the response will contain corresponding error.

=head2 response

	$smtp->on(response => sub {
		my ($smtp, $cmd, $resp) = @_;
		if ($cmd == Mojo::SMTP::Client::CMD_CONNECT) {
			# and after first response others should be fast enough
			$smtp->inactivity_timeout(10);
		}
	});

Emitted for each SMTP response from the server. C<$cmd> is a command L<constant|/CONSTANTS> for which this
response was sent. C<$resp> is L<Mojo::SMTP::Client::Response> object. You can interrupt sending by dying or
throwing an exception from this callback, C<error> attribute of the response will contain corresponding error.

=head1 ATTRIBUTES

C<Mojo::SMTP::Client> implements the following attributes, which you can set in the constructor or get/set later
with object method call

=head2 address

Address of SMTP server (ip or domain name). Default is C<localhost>

=head2 port

Port of SMTP server. Default is C<25> for plain connection and C<465> if TLS is enabled.

=head2 tls

Enable TLS. Should be true if SMTP server expects encrypted connection. Default is false.
Proper version of L<IO::Socket::SSL> should be installed for TLS support in L<Mojo::IOLoop::Client>,
which you can find with C<mojo version> command.

=head2 tls_ca

Path to TLS certificate authority file. Also activates hostname verification.

=head2 tls_cert

Path to the TLS certificate file.

=head2 tls_key

Path to the TLS key file.

=head2 tls_verify

TLS verification mode. Use C<0> to disable verification, which turned on by default.

=head2 hello

SMTP requires that you identify yourself. This option specifies a string to pass as your mail domain.
Default is C<localhost.localdomain>

=head2 connect_timeout

Maximum amount of time in seconds establishing a connection may take before getting canceled,
defaults to the value of the C<MOJO_CONNECT_TIMEOUT> environment variable or C<10>

=head2 inactivity_timeout

Maximum amount of time in seconds a connection can be inactive before getting closed,
defaults to the value of the C<MOJO_INACTIVITY_TIMEOUT> environment variable or C<20>.
Setting the value to C<0> will allow connections to be inactive indefinitely

=head2 ioloop

Event loop object to use for blocking I/O operations, defaults to a L<Mojo::IOLoop> object

=head2 autodie

Defines should or not C<Mojo::SMTP::Client> throw exceptions for any type of errors. This only usable for
blocking usage of C<Mojo::SMTP::Client>, because non-blocking one should never die. Throwed
exception will be one of the specified in L<Mojo::SMTP::Client::Exception>. When autodie attribute
has false value you should check C<$respE<gt>error> yourself. Default is false.

=head1 METHODS

C<Mojo::SMTP::Client> inherits all methods from L<Mojo::EventEmitter> and implements the following new ones

=head2 send

	$smtp->send(
		from => $mail_from,
		to   => $rcpt_to,
		data => $data,
		quit => 1,
		$nonblocking ? $cb : ()
	);

Send specified commands to SMTP server. Arguments should be C<key =E<gt> value> pairs where C<key> is a command 
and C<value> is a value for this command. C<send> understands the following commands:

=over

=item hello

Send greeting to the server. Argument to this command should contain your domain name. Keep in mind, that
C<Mojo::SMTP::Client> will automatically send greeting to the server right after connection if you not specified
C<hello> as first command for C<send>. C<Mojo::SMTP::Client> first tries C<EHLO> command for greeting and if
server doesn't accept it C<Mojo::SMTP::Client> retries with C<HELO> command.

	$smtp->send(hello => 'mymail.me');

=item starttls

Upgrades connection from plain to encrypted. Some servers requires this before sending any other commands.
L<IO::Socket::SSL> 0.98+ should be installed for this to work. See also L</tls_ca>, L</tls_cert>, L</tls_key>
attributes

	$smtp->tls_ca('/etc/ssl/certs/ca-certificates.crt');
	$smtp->send(starttls => 1);

=item auth

Authorize on SMTP server. Argument to this command should be a reference to a hash with C<type>,
C<login> and C<password> keys. Only PLAIN and LOGIN authorization are supported as C<type> for now.
You should authorize only once per session.

    $smtp->send(auth => {login => 'oleg', password => 'qwerty'});      # defaults to AUTH PLAIN
    $smtp->send(auth => {login => 'oleg', password => 'qwerty', type => 'login'}); # AUTH LOGIN

=item from

From which email this message was sent. Value for this cammand should be a string with email

	$smtp->send(from => 'root@cpan.org');

=item to

To which email(s) this message should be sent. Value for this cammand should be a string with email
or reference to array with email strings (for more than one recipient)

	$smtp->send(to => 'oleg@cpan.org');
	$smtp->send(to => ['oleg@cpan.org', 'do_not_reply@cpantesters.org']);

=item reset

After this command server should forget about any started mail transaction and reset it status as it was after response to C<EHLO>/C<HELO>.
Note: transaction considered started after C<MAIL FROM> (C<from>) command.

	$smtp->send(reset => 1);

=item data

Email body to be sent. Value for this command should be a string (or reference to a string) with email body or reference to subroutine
each call of which should return some chunk of the email as string (or reference to a string) and empty string (or reference to empty string)
at the end (useful to send big emails in memory-efficient way)

	$smtp->send(data => "Subject: This is my first message\r\n\r\nSent from Mojolicious app");
	$smtp->send(data => sub { sysread(DATA, my $buf, 1024); $buf });

=item quit

Send C<QUIT> command to SMTP server which will close the connection. So for the next use of this server connection will be
reestablished. If you want to send several emails with this server it will be more efficient to not quit
the connection until last email will be sent.

=back

For non-blocking usage last argument to C<send> should be reference to subroutine which will be called when result will
be available. Subroutine arguments will be C<($smtp, $resp)>. Where C<$resp> is object of L<Mojo::SMTP::Client::Response> class.
First you should check C<$resp-E<gt>error> - if it has true value this means that it was error somewhere while sending.
If C<error> has false value you can get code and message for response to last command with C<$resp-E<gt>code> (number) and
C<$resp-E<gt>message> (string).

For blocking usage C<$resp> will be returned as result of C<$smtp-E<gt>send> call. C<$resp> is the same as for
non-blocking result. If L</autodie> attribute has true value C<send> will throw an exception on any error.



( run in 1.910 second using v1.01-cache-2.11-cpan-39bf76dae61 )